理解this機制的第一步首先是要明白 this既不指向函數(shù)自身也不指向函數(shù)的詞法作用域疚脐,this實際上是在函數(shù)被調(diào)用時發(fā)生綁定的萝毛,所以要先了解調(diào)用棧和函數(shù)的調(diào)用位置须喂。
調(diào)用棧就是為了達(dá)到當(dāng)前執(zhí)行位置而調(diào)用的所有函數(shù)责语,而調(diào)用位置就在當(dāng)前執(zhí)行函數(shù)的前一個調(diào)用中贴硫。而調(diào)用位置決定了this的綁定椿每。
function fn1(){
//當(dāng)前調(diào)用棧是:fn1
//當(dāng)前調(diào)用位置是全局作用域
console.log("fn1");
fn2();//fn2的調(diào)用位置
}
function fn2(){
//當(dāng)前調(diào)用棧是: fn1 => fn2
//當(dāng)前調(diào)用位置在 fn1中
console.log("fn2");
}
fn1() //fn1的調(diào)用位置
那么調(diào)用位置是如何決定this的綁定的呢?
在調(diào)用位置中英遭,this綁定由以下四條規(guī)則來判定綁定對象:
1跑揉、默認(rèn)綁定
如果函數(shù)是直接使用不帶任何修身的函數(shù)引用進(jìn)行調(diào)用的,只能使用默認(rèn)規(guī)則带兜,無法應(yīng)用其他規(guī)則谅摄,在下面例子中,this是默認(rèn)綁定到全局對象多律,如果是在嚴(yán)格模式下痴突,this就會默認(rèn)綁定到undefined.
function fn1(){
console.log(this.a);
}
var a = 1;
fn1() // 2
2、隱式綁定
如果調(diào)用位置有上下文對象狼荞,就會應(yīng)用隱式綁定規(guī)則辽装,this就會被綁定到上下文對象上相味。
function fn(){
console.log(this.a);
}
var obj = {
a: 2,
fn: fn
};
obj.fn() // 2
當(dāng)fn()被調(diào)用時候,它是指向obj對象的,隱式規(guī)則會把函數(shù)調(diào)用中的this綁定到這個上下文對象上斯碌。
隱式丟失
但是有時候被隱式綁定的函數(shù)會丟失綁定對象。
接上述代碼
var fnn() = obj.foo;
var a = 3;
fnn();//3
雖然fnn是obj.foo的一個引用玲销,但其實它調(diào)用的是fn()函數(shù)本身,且不帶任何修飾策吠,自動應(yīng)用默認(rèn)綁定規(guī)則,將this綁定到了全局對象中猴抹。這個隱式丟失問題有沒有解決辦法呢,請繼續(xù)往下看蟀给。
3、顯式綁定
有隱式綁定就會有顯式綁定阳堕,就像有光就有暗一樣跋理,當(dāng)我們不想在一個對象里引用函數(shù),而是直接強制調(diào)用函數(shù)時該怎么做呢恬总?這里我們就是用js一些特殊函數(shù)來達(dá)成這樣的效果前普。例如apply()和call(),它們的第一個參數(shù)都是一個對象,它們會把this綁定到這個對象壹堰,接著在函數(shù)調(diào)用時指向這個this拭卿。
可是顯式綁定還是無法解決隱式丟失的問題,但是它的一個變種——硬綁定贱纠,可以解決這個問題峻厚。
function fn(){
console.log(this.a);
}
var obj = {
a: 2,
};
var fnn() = function(){
fn.call(obj)
};
fnn();//2
fnn.call(window) // 2 硬綁定的函數(shù)不能再改變它的this綁定。
由于硬綁定比較常用谆焊,ES5提供了內(nèi)置的方法:Function.prototype.bind惠桃,還有一種實現(xiàn)方式是
許多第三方庫以及許多內(nèi)置的API的“上下文”,即內(nèi)置函數(shù)的一些可選參數(shù)辖试。
function fn(e){
console.log(e,this.a);
}
var obj = {
a: "mama"
};
[1,2,3].forEach(fn,obj) // 1 mama 2 mama 3 mama
實際上它們也是通過apply()和call()來實現(xiàn)顯式綁定的辜王。
4、new綁定
這是第四條也是最后一條綁定規(guī)則剃执,new綁定誓禁。
js所有的函數(shù)包括內(nèi)置函數(shù)都可以用new調(diào)用懈息,被稱為構(gòu)造函數(shù)調(diào)用肾档,其實js并不存在所謂的“構(gòu)造函數(shù)”,只有對于函數(shù)的“構(gòu)造調(diào)用”
發(fā)生構(gòu)造調(diào)用,即用new來調(diào)用時怒见,會自動執(zhí)行以下操作:
1俗慈、創(chuàng)建一個全新的對象
2、這個新對象會被執(zhí)行原型連接
3遣耍、這個新對象會被綁定到函數(shù)調(diào)用的this上
4闺阱、如果函數(shù)沒有返回其他對象,那么會自動返回這個新的對象
判斷this綁定
現(xiàn)在我們可以根據(jù)優(yōu)先級來判斷函數(shù)在調(diào)用位置應(yīng)用的是哪個規(guī)則:
1舵变、如果函數(shù)是在new中調(diào)用酣溃,this綁定的是新創(chuàng)建的對象
2纪隙、如果函數(shù)通過apply()、call()或硬綁定碘饼,this綁定的是指定的對象
3艾恼、如果函數(shù)是在某個上下文對象中調(diào)用钠绍,this綁定的是這個上下文對象
4五慈、如果都不是則使用默認(rèn)綁定泻拦,非嚴(yán)格模式下this綁定全局對象争拐,嚴(yán)格模式下綁定undefined
被忽略的this
如果你把null或者undefined作為this 的綁定對象傳入call()架曹、apply()或bind()中闹瞧,這些值在調(diào)用時會被忽略奥邮,實際使用的還是默認(rèn)綁定規(guī)則。
ES6箭頭函數(shù)
之前介紹的四條規(guī)則可以應(yīng)用所以的正常函數(shù)脚粟,但是ES6的箭頭函數(shù)是一種無法使用這些規(guī)則的特殊函數(shù)類型核无。它并不是function關(guān)鍵字定義的团南,并不使用這四條規(guī)則吐根,而是由外層作用域來決定this。
實際上在ES6之前我們就已經(jīng)在使用一種幾乎和箭頭函數(shù)完全一樣的模式朋腋,就是使用 self = this旭咽。從本質(zhì)上來說穷绵,它們都是想要替代this機制仲墨。
this綁定的水還很深目养,這里只是介紹了一些粗淺的綁定規(guī)則知識癌蚁,更深入的部分我也還沒學(xué)會兜畸! :)