this 指針詳解
概念
this 是當(dāng)前函數(shù)/當(dāng)前模塊運(yùn)行環(huán)境的上下文铣缠,是一個(gè)指針變量,普通函數(shù)中的this 是在調(diào)用時(shí)才被綁定確認(rèn)指向的鹦付。
通過(guò)不同的 this 調(diào)用同一個(gè)函數(shù)尚粘,可以產(chǎn)出不同的結(jié)果
到底如何確認(rèn) this 綁定的內(nèi)容
this 的綁定規(guī)則
1. 默認(rèn)綁定
直接調(diào)用函數(shù),不使用點(diǎn)操作符調(diào)用
非嚴(yán)格模式下指向全局對(duì)象敲长,瀏覽器環(huán)境是 window郎嫁,node 環(huán)境是 global
嚴(yán)格模式下綁定到 undefined。
Tips: 普通函數(shù)做為參數(shù)傳遞的情況, 比如setTimeout, setInterval, 非嚴(yán)格模式下的this指向全局對(duì)象
2. 隱式綁定
某個(gè)對(duì)象通過(guò)點(diǎn)運(yùn)算符調(diào)用函數(shù)祈噪。this 指向該對(duì)象泽铛。
3. 顯示綁定
通過(guò) bind、call钳降、apply 把函數(shù)綁定到指定的變量上厚宰。
- bind 展開(kāi)傳參,只綁定不調(diào)用,返回一個(gè)新函數(shù)
- call 展開(kāi)傳參铲觉,綁定的同時(shí)執(zhí)行函數(shù)
- apply 傳入?yún)?shù)列表澈蝙,綁定的同時(shí)執(zhí)行函數(shù)
function person (name, sex) {
console.log(this, name, sex)
}
const obj = {
name: 'ss',
sex: 'M'
}
person.call(obj, 'ss', 'M')
person.apply(obj, ['ss', 'M'])
const b = person.bind(obj, 'ss')
b()
Tips:如果我們傳入 call/apply/bind 的第一個(gè)參數(shù)是一個(gè)數(shù)字、布爾值撵幽、string 等基本數(shù)據(jù)類(lèi)型灯荧,綁定時(shí)會(huì)裝箱成一個(gè)對(duì)象
手寫(xiě) bind
Function.prototype.bind2 = function (context) {
context = context || window
const fnSymbol = Symbol('fn')
context[fnSymbol] = this
let args = Array.prototype.slice.call(arguments, 1)
return function () {
args = args.concat(Array.from(arguments))
context[fnSymbol](...args)
delete context[fnSymbol]
}
}
function foo (a, b) {
console.log(this, a, b)
}
const obj = {
name: 'ss',
sex: 'M'
}
const f2 = foo.bind2(obj, 'hahah')
f2('ss')
const f3 = foo.bind(obj, 'hahah')
f3('ss')
call
Function.prototype.call2 = function (context, ...args) {
context = Object(context || window)
const fnSymbol = Symbol('fn')
context[fnSymbol] = this
const result = context[fnSymbol](...args)
delete context[fnSymbol]
return result
}
function foo (a, b) {
console.log(this, a, b)
}
const obj = {
name: 'ss'
}
foo.call2(obj, 3, 5)
console.log(obj)
foo.call(obj, 3, 5)
4. new 綁定
new 的功能
- 創(chuàng)建一個(gè)空對(duì)象
- 將對(duì)象的proto 屬性指向構(gòu)造函數(shù)的 prototype
- 將構(gòu)造函數(shù)的 this 指向該新建對(duì)象,執(zhí)行構(gòu)造函數(shù)
- 如果構(gòu)造函數(shù)執(zhí)行結(jié)果為對(duì)象盐杂,則返回該對(duì)象逗载,否則返回之前新建的對(duì)象
構(gòu)造函數(shù)中的 this 指向了新生成的實(shí)例對(duì)象 studyDay
function study (name) {
this.name = name
}
const studyDay = new study('ss')
console.log(studyDay) // { name: 'ss' }
console.log(studyDay.name) // ss
5. this 綁定的優(yōu)先級(jí)
new > 顯式 > 隱式 > 默認(rèn)
箭頭函數(shù)
- 箭頭函數(shù)沒(méi)有 arguments
- 箭頭函數(shù)不能用作構(gòu)造函數(shù)
- 箭頭函數(shù)沒(méi)有原型對(duì)象
let fun = () => {}
console.log(fun.prototype) // undefined
- 箭頭函數(shù)沒(méi)有自己的 this
閉包的概念及應(yīng)用場(chǎng)景
閉包是能訪問(wèn)自由變量的函數(shù)
自由變量是指在函數(shù)中使用的,既不是函數(shù)局部變量也不是函數(shù)參數(shù)的變量
- 從理論角度:所有的函數(shù)都是閉包链烈。因?yàn)樗麄兌际窃趧?chuàng)建的時(shí)候就將上層上下文數(shù)據(jù)保存起來(lái)了厉斟。哪怕是簡(jiǎn)單的全局變量也是如此,因?yàn)楹瘮?shù)中訪問(wèn)全局變量就相當(dāng)于在訪問(wèn)自由變量强衡,這個(gè)時(shí)候使用最外層的作用域
- 從實(shí)踐角度:以下函數(shù)才算是閉包:
- 即使創(chuàng)建他的上下文已經(jīng)銷(xiāo)毀擦秽,他仍然存在(比如內(nèi)部函數(shù)從父函數(shù)中返回)
- 在代碼中引用了自由變量
應(yīng)用場(chǎng)景
- 柯里化的目的在于:避免頻繁調(diào)用傳相同參數(shù)的函數(shù)。同時(shí)又能夠輕松復(fù)用漩勤。
其實(shí)就是封裝一個(gè)高階函數(shù)
function getArea(width, height) {
return width * height
}
function getWidthArea(width) {
return function (height) {
return width * height
}
}
const getArea2 = getWidthArea(2)
console.log(getArea2(6))
const getArea10 = getWidthArea(10)
console.log(getArea10(11))
- 使用閉包實(shí)現(xiàn)私有方法和變量
function Person () {
let _name = 'ss'
return {
getName() {
return _name
},
setName(name) {
_name = name
}
}
}
const person = Person()
person.setName('xy')
console.log(person.getName())
- 匿名自執(zhí)行函數(shù)
const funcOne = (function(){
let i = 0
return () => {
i++
console.log(i)
}
})()
funcOne() // 1
funcOne() // 2
funcOne() // 3
- 緩存一些結(jié)果
比如在外部函數(shù)創(chuàng)建一個(gè)數(shù)組感挥,閉包函數(shù)內(nèi)可以更改/獲取這個(gè)數(shù)組的值,其實(shí)還是延長(zhǎng)變量的聲明周期越败,但是不通過(guò)全局變量來(lái)實(shí)現(xiàn)触幼。
function funParent () {
let memo = []
function funTwo (i) {
memo.push(i)
console.log(memo.join(,))
}
return funTwo
}
const fn = funParent()
fn(1)
fn(2)
總結(jié)
- 創(chuàng)建私有變量
- 延長(zhǎng)變量的生命周期
一般函數(shù)的詞法環(huán)境在函數(shù)返回后就被銷(xiāo)毀了,但是閉包會(huì)保存對(duì)創(chuàng)建時(shí)所在詞法環(huán)境的引用究飞,即便創(chuàng)建時(shí)所在的執(zhí)行上下文被銷(xiāo)毀置谦,但創(chuàng)建時(shí)所在詞法環(huán)境依然存在,以達(dá)到延長(zhǎng)變量的生命周期的目的
代碼題
- 實(shí)現(xiàn) compose 函數(shù)噪猾,得到如下輸出
function fn1(x) {
return x + 1
}
function fn2(x) {
return x + 2
}
function fn3(x) {
return x + 3
}
function fn4(x) {
return x + 4
}
const a = compose(fn1, fn2, fn3, fn4);
console.log(a('1')); // 1+4+3+2+1=11
function compose (...fns) {
fns = fns.reverse()
return (x) => {
return fns.reduce((pre, cur) => cur(pre), x)
}
}
- 實(shí)現(xiàn)一個(gè)柯里化函數(shù)
function currying(fn, ...args) {
const fnArgLength = fn.length
const retFunc = (..._args) => {
args = args.concat(_args)
return args.length >= fnArgLength
? fn(...args)
: retFunc
}
return retFunc
}
const add = (a, b, c) => a + b + c;
const a1 = currying(add, 1, 2, 3, 4);
const a2 = a1();
console.log(a2) // 6
作用域
作用域是在運(yùn)行時(shí)代碼中的某些特定部分中變量霉祸,函數(shù)和對(duì)象的可訪問(wèn)性。
換句話說(shuō)袱蜡,作用域決定了代碼區(qū)塊中變量和其他資源的可見(jiàn)性
作用域就是一個(gè)獨(dú)立的地盤(pán)丝蹭,讓變量不會(huì)外泄、暴露出去坪蚁。也就是說(shuō)奔穿,作用域最大的用處就是隔離變量,不同作用域下同名變量不會(huì)有沖突敏晤。
ES6 之前 JavaScript 沒(méi)有塊級(jí)作用域贱田,只有全局作用域和函數(shù)作用域。ES6 的到來(lái)嘴脾,為我們提供了塊級(jí)作用域男摧,可通過(guò)let
和const
來(lái)定義塊級(jí)作用域變量
全局作用域
在代碼中任何地方都可以訪問(wèn)變量和函數(shù)擁有全局作用域
- 最外層函數(shù) 和 定義在最外層函數(shù)外面的變量 有全局作用域
var outVariable = "我是最外層變量"; //最外層變量
function outFun() { //最外層函數(shù)
var inVariable = "內(nèi)層變量";
function innerFun() { //內(nèi)層函數(shù)
console.log(inVariable);
}
innerFun();
}
console.log(outVariable); //我是最外層變量
outFun(); //內(nèi)層變量
console.log(inVariable); //inVariable is not defined
innerFun(); //innerFun is not defined
- 所有未定義直接賦值的變量自動(dòng)聲明為擁有全局作用域的變量
function outFunc2() {
variable = '未定義直接復(fù)制的變量'
var inVariable2 = '內(nèi)層變量2'
}
outFunc()
console.log(variable) // 未定義直接復(fù)制的變量
console.log(inVariable2); //inVariable2 is not defined
- 所有 window 對(duì)象的屬性都有全局作用域
window.location
- 弊端
定義全局變量容易造成全局命名空間污染蔬墩,引起變量沖突
函數(shù)作用域
函數(shù)作用域,是指聲明在函數(shù)內(nèi)部的變量耗拓,只有在函數(shù)內(nèi)部才能訪問(wèn)到
作用域是分層的拇颅,內(nèi)層作用域可以訪問(wèn)外層作用域的變量,反之不行
塊級(jí)作用域
塊級(jí)作用域可通過(guò)新增命令 let 和 const 聲明乔询,所聲明的變量在指定塊的作用域外無(wú)法被訪問(wèn)樟插。塊級(jí)作用域 在如下情況下被創(chuàng)建:
- 在一個(gè)函數(shù)內(nèi)部
- 在一個(gè)代碼塊(由一對(duì)花括號(hào)包裹)內(nèi)部
let 聲明的語(yǔ)法與 var 的語(yǔ)法一致。你基本上可以用 let 來(lái)代替 var 進(jìn)行變量聲明竿刁,但會(huì)將變量的作用域限制在當(dāng)前代碼塊中黄锤。
塊級(jí)作用域有以下幾個(gè)特點(diǎn):
- 聲明變量不會(huì)提升到代碼塊頂部
- 禁止重復(fù)聲明
- 變量只在當(dāng)前塊內(nèi)有效
作用域鏈
有點(diǎn)兒類(lèi)似于原型鏈,在原型鏈上早一個(gè)屬性的時(shí)候食拜,如果當(dāng)前實(shí)例找不到鸵熟,就回去父級(jí)原型上去找。
作用域鏈也是類(lèi)似的原理负甸,找一個(gè)變量的時(shí)候旅赢,如果當(dāng)前作用域找不到,就會(huì)逐級(jí)網(wǎng)上查找惑惶,找到找到全局作用域還是沒(méi)有找到,就真的找不到了短纵。
Tips: 函數(shù)內(nèi)有效的作用域鏈值指的是創(chuàng)建函數(shù)時(shí)的作用域鏈带污,不是執(zhí)行函數(shù)時(shí)的作用域鏈
const foo = (function() {
let a = 1
return function() {
console.log(a)
}
})();
(function(){
let a = 3
foo()
})()
// 1
const foo = (function() {
// let a = 1
return function() {
console.log(a)
}
})();
(function(){
let a = 3
foo()
})()
// a is not defined
const foo = (function() {
// let a = 1
return function() {
console.log(a)
}
})();
let a = 2;
(function(){
let a = 3
foo()
})()
// 2
Coding
- 看一下輸出
var b = 10;
(function b(){
b = 20;
// 內(nèi)部作用域,會(huì)先去查找是有已有變量b的聲明香到,有就直接賦值20鱼冀,確實(shí)有了呀。發(fā)現(xiàn)了具名函數(shù) function b(){}悠就,拿此b做賦值千绪;
// IIFE的函數(shù)無(wú)法進(jìn)行賦值(內(nèi)部機(jī)制,類(lèi)似const定義的常量)梗脾,所以無(wú)效荸型。
console.log(b); // fn b
console.log(window.b); // 10
})();
- IIFE 中的函數(shù)是函數(shù)表達(dá)式不是函數(shù)聲明
- 函數(shù)表達(dá)式與函數(shù)聲明不同,函數(shù)名只在函數(shù)內(nèi)部有效炸茧,并且此綁定是常量綁定
- 對(duì)一個(gè)常量賦值瑞妇,在嚴(yán)格模式下報(bào)錯(cuò),非嚴(yán)格模式靜默失敗
- 看一下輸出
var a = 3;
function c() {
alert(a);
}
(function () {
var a = 4;
c(); // 3
})();
- 看一下輸出
function v() {
var a = 6
function a () {
}
console.log(a) // 6
}
js會(huì)把所有變量都集中提升到作用域頂部事先聲明好梭冠,但是它賦值的時(shí)機(jī)是依賴(lài)于代碼的位置辕狰,那么js解析運(yùn)行到那一行之后才會(huì)進(jìn)行賦值,還沒(méi)有運(yùn)行到的就不會(huì)事先賦值控漠。也就是變量會(huì)事先聲明蔓倍,但是變量不會(huì)事先賦值。
碰到這種問(wèn)題可以先想一下變量提升和函數(shù)聲明提升的規(guī)則, 原則上是變量被提升到最頂部, 函數(shù)聲明被提升到最頂部變量的下方.
嘗試著把這兩段代碼在大腦中編譯一下:
- 第一段代碼
function v() {
var a;
function a() {
}
a=6;
console.log(a);
}
v(); // 6
- 第二段代碼
function v() {
var a;
function a() {
}
console.log(a);
}
v(); // fn a
- 看一下輸出
function v() {
console.log(a); // fn a
var a = 1;
console.log(a); // 1
function a() {
}
console.log(a); // 1
console.log(b); // fn b
var b = 2;
console.log(b); // 2
function b() {
}
console.log(b); // 2
}
v();
按照剛才的思路轉(zhuǎn)換一下:
function v() {
var a;
var b;
function a() {}
function b() {}
console.log(a); // fn a
a=1;
console.log(a); // 1
console.log(a); // 1
console.log(b); // fn b
b=2;
console.log(b); // 2
console.log(b); // 2
}
v();