前置知識:作用域與this
要想徹底明白 的行為,就要明白作用域和 的關(guān)系走芋。
中采用的詞法作用域全闷,即在函數(shù)聲明時(shí)叉寂,就已經(jīng)確定了其作用域,而與函數(shù)在任何處調(diào)用沒有關(guān)系总珠;而 是在運(yùn)行時(shí)確定的屏鳍,因此對于同一個(gè)函數(shù)而言,不同的調(diào)用方式會導(dǎo)致不同的 綁定(暫時(shí)不考慮箭頭函數(shù)局服,下文會單獨(dú)講)孕蝉。這一點(diǎn)對于this
很重要。
四種綁定方式
在各種書籍和博客上都會提到 的綁定方式有四種腌逢,分別如下(長不了考慮箭頭函數(shù))
- 默認(rèn)綁定
- 隱式綁定
- 顯示綁定
- 綁定
1降淮、默認(rèn)綁定
默認(rèn)綁定是指,當(dāng)函數(shù)獨(dú)立調(diào)用時(shí)(即使沒有調(diào)用者)搏讶, 指向
// 代碼1.1
function foo(){
console.log(this)
}
foo() // window
而在嚴(yán)格模式下 為
// 代碼1.2
'use strict'
function foo(){
console.log(this)
}
foo() // undefined
這種綁定形式很好理解佳鳖,此外,值得注意的是媒惕,立即執(zhí)行函數(shù)表達(dá)式 也是函數(shù)獨(dú)立調(diào)用的一種系吩,其中也會指向window
// 代碼1.3
(function(){
console.log(this) //window
})()
下面這種形式也是函數(shù)獨(dú)立調(diào)用,指向
代碼1.4
function bar(){
console.log(this)
}
let obj = {
foo:function(){
bar();
}
}
obj.foo() // window
2妒蔚、隱式綁定
隱式綁定與默認(rèn)綁定相對應(yīng)穿挨,隱式綁定 可以大致理解為誰調(diào)用,就指向誰肴盏,即指向當(dāng)前執(zhí)行上下文科盛。
// 代碼2.1
let obj ={
foo:function(){
console.log(this)
}
}
obj.foo() // obj
上面的代碼中,先在全局聲明了函數(shù)菜皂,之后將其賦值給的foo方法贞绵。再次強(qiáng)調(diào),是執(zhí)行的時(shí)候確定恍飘,與作用域無關(guān)榨崩。因此谴垫,雖然函數(shù)是在全局聲明的,但是調(diào)用者是obj母蛛,也就是執(zhí)行時(shí)上下文對象翩剪,因此指向對象。
是不是感覺自己懂了彩郊?別急前弯,再看看下邊的代碼
// 代碼2.3
let obj ={
foo:function(){
console.log(this) // window
}
};
let bar = obj.foo
bar();
obj.foo()
解析如下:
- 首先不要一看到函數(shù),就開始在腦海中運(yùn)行焦辅,只要把它看作是一個(gè)變量就好博杖,不要考慮函數(shù)體里面的代碼,等到執(zhí)行時(shí)再去進(jìn)行解析筷登。
- obj.off方法聲明部分剃根,只需要理解為在堆內(nèi)存中開辟了一塊空間,并由obj.foo持有這塊內(nèi)存空間的作用前方,由于函數(shù)尚未執(zhí)行狈醉,因此還沒有確定
- 將賦值給,也就是將函數(shù)的引用拷貝一份給bar
- 獨(dú)立調(diào)用惠险,因此this指向window
這也就是很多文章中提到的隱式丟失苗傅。
代碼2.3中的this
隱式丟失是由于變量賦值導(dǎo)致的,此外班巩,間接引用也會造成隱式丟失渣慕。
// 代碼2.4
function logThis(){
console.log(this)
}
let obj1 = { log: logThis }
let obj2 ={ }
obj1.log(); //obj1
(obj2.log = obj1.log)() // window
這是由于賦值表達(dá)式(obj2.log = obj1.log)
的返回值是函數(shù)logThis
的引用,上面的代碼可以理解為
let retFn = (obj2.log = obj1.log)
retFn();
可以看出抱慌,這相當(dāng)于函數(shù)獨(dú)立調(diào)用 符合默認(rèn)綁定的規(guī)則逊桦。
還有一種比較隱蔽的導(dǎo)致隱式丟失的情況:
//代碼2.5
let obj ={
foo:function(){
console.log(this)
}
}
function bar(cb){
cb();
}
bar(obj.foo) // window
答案同樣是window
,這就是由于參數(shù)傳遞導(dǎo)致的this
隱式丟失抑进,分析如下强经。
- 聲明,在堆內(nèi)存中開辟一塊空間寺渗,并由持有對內(nèi)存的引用匿情。
- 將作為函數(shù)的參數(shù),獨(dú)立調(diào)用信殊,函數(shù)執(zhí)行過程如下所示炬称;
1、在函數(shù)作用域內(nèi)聲明cb
,并將實(shí)參賦值給形參cb
2鸡号、cb
獨(dú)立調(diào)用转砖。
從以上的分析可以看出,參數(shù)傳遞導(dǎo)致的隱式丟失與變量賦值導(dǎo)致的隱式丟失鲸伴,從本質(zhì)上來說是一樣的府蔗。
參數(shù)傳遞導(dǎo)致的this隱式丟失在JavaScript
內(nèi)置API中也十分常見,比如:Array.prototype.map
汞窗,setTimeout
姓赤,ES6的Promise
構(gòu)造函數(shù),node中的readFile等等仲吏。這些函數(shù)接受一個(gè)回調(diào)函數(shù)作為參數(shù)不铆,并在某一時(shí)刻執(zhí)行它,執(zhí)行模式與下面的偽代碼類似
// 代碼2.5
function fn(cb) {
// {...代碼...}
cb()
// {...代碼...}
}
參數(shù)傳遞導(dǎo)致的隱式丟失比較隱蔽裹唆,不容易發(fā)現(xiàn)誓斥,需要額外留意。
this
隱式綁定是指誰調(diào)用许帐,this
就指向誰
經(jīng)常與隱式綁定一同出現(xiàn)的是隱式丟失劳坑,隱式丟失常見于三種情況,變量賦值成畦,簡介引用和參數(shù)傳遞(常見于回調(diào)函數(shù))
3距芬、顯示綁定
最常見的顯示綁定就是上的三個(gè)方法:、循帐、框仔。
相信這幾個(gè)方法日常用的就比較多了吧,也就不做過多的贅述拄养。但是有一點(diǎn)還是要注意离斩,就是通過以上三個(gè)方法綁定后的this
之后,就無法通過這三個(gè)方法繼續(xù)改變this
指向了瘪匿,也就是說跛梗,只有一次綁定有效,后續(xù)綁定就會失敗柿顶。
有一個(gè)點(diǎn)需要注意茄袖,就是返回的bound
沒有prototype
屬性,但是可以用let ins = new bound()
來創(chuàng)建來創(chuàng)建對象嘁锯。并且ins instanceof bound
為 true
宪祥,這一點(diǎn)規(guī)范中有提到,如果instanceof
的對象是一個(gè)bind
之后的函數(shù)家乘,就會找原來的函數(shù)進(jìn)行操作蝗羊。
除了上面提到的三個(gè)顯式綁定的方法,還有另外兩種綁定方式仁锯,我也不知道該怎么分類耀找,姑且放到顯式綁定里吧:事件的綁定和部分API提供的綁定方式。
第一種是事件處理函數(shù),在事件處理函數(shù)中野芒,this
指向綁定事件的元素蓄愁。第二種是例如Array.prototype.map
支持第二個(gè)參數(shù)來指定回調(diào)函數(shù)中的this指向。值得一提的是狞悲,通過bind
可以改變這兩種情況中函數(shù)的this
指向撮抓。
4、new綁定
當(dāng)使用new
來實(shí)例化一個(gè)構(gòu)造函數(shù)時(shí)摇锋,this
指向?qū)嵗?/p>
優(yōu)先級
綁定的優(yōu)先級就比較簡單了丹拯,這四種方式從上到下權(quán)重依次增加:new > 顯式綁定 > 隱式綁定 > 默認(rèn)綁定
箭頭函數(shù)
前面說了這么多,都是針對普通函數(shù)荸恕,也就是用function
關(guān)鍵字聲明的函數(shù)乖酬。ES6中引入了箭頭函數(shù),都說箭頭函數(shù)沒有this
融求,但是又能在箭頭函數(shù)中使用this咬像,這可能會給很多人造成困惑。其實(shí)双肤,弄清楚箭頭函數(shù)的this
很簡單施掏,只需要把箭頭函數(shù)的this
看做一個(gè)普通的變量,由于箭頭函數(shù)自身沒有this茅糜,所以需要沿著作用域鏈向上進(jìn)行查找七芭,直到找到this
(普通函數(shù)或者window)。也就是說蔑赘,箭頭函數(shù)的this
由兩方面決定:詞法作用域和父級函數(shù)的this狸驳。因此,理解了普通函數(shù)的this
指向缩赛,箭頭函數(shù)的this
也就很簡單了耙箍。
那么箭頭函數(shù)和call
一起使用會發(fā)生什么呢?由于箭頭函數(shù)自身是沒有this
的酥馍,而call是改變目標(biāo)函數(shù)的this指向辩昆,因此對箭頭函數(shù)使用call
是無效的,箭頭函數(shù)依然會根據(jù)詞法作用域來查找this
旨袒。