理解this機制的第一步首先是要明白 this既不指向函數自身也不指向函數的詞法作用域,this實際上是在函數被調用時發生綁定的,所以要先了解調用棧和函數的調用位置。
調用棧就是為了達到當前執行位置而調用的所有函數,而調用位置就在當前執行函數的前一個調用中。而調用位置決定了this的綁定。
function fn1(){
//當前調用棧是:fn1
//當前調用位置是全局作用域
console.log("fn1");
fn2();//fn2的調用位置
}
function fn2(){
//當前調用棧是: fn1 => fn2
//當前調用位置在 fn1中
console.log("fn2");
}
fn1() //fn1的調用位置
那么調用位置是如何決定this的綁定的呢?
在調用位置中,this綁定由以下四條規則來判定綁定對象:
1、默認綁定
如果函數是直接使用不帶任何修身的函數引用進行調用的,只能使用默認規則,無法應用其他規則,在下面例子中,this是默認綁定到全局對象,如果是在嚴格模式下,this就會默認綁定到undefined.
function fn1(){
console.log(this.a);
}
var a = 1;
fn1() // 2
2、隱式綁定
如果調用位置有上下文對象,就會應用隱式綁定規則,this就會被綁定到上下文對象上。
function fn(){
console.log(this.a);
}
var obj = {
a: 2,
fn: fn
};
obj.fn() // 2
當fn()被調用時候,它是指向obj對象的,隱式規則會把函數調用中的this綁定到這個上下文對象上。
隱式丟失
但是有時候被隱式綁定的函數會丟失綁定對象。
接上述代碼
var fnn() = obj.foo;
var a = 3;
fnn();//3
雖然fnn是obj.foo的一個引用,但其實它調用的是fn()函數本身,且不帶任何修飾,自動應用默認綁定規則,將this綁定到了全局對象中。這個隱式丟失問題有沒有解決辦法呢,請繼續往下看。
3、顯式綁定
有隱式綁定就會有顯式綁定,就像有光就有暗一樣,當我們不想在一個對象里引用函數,而是直接強制調用函數時該怎么做呢?這里我們就是用js一些特殊函數來達成這樣的效果。例如apply()和call(),它們的第一個參數都是一個對象,它們會把this綁定到這個對象,接著在函數調用時指向這個this。
可是顯式綁定還是無法解決隱式丟失的問題,但是它的一個變種——硬綁定,可以解決這個問題。
function fn(){
console.log(this.a);
}
var obj = {
a: 2,
};
var fnn() = function(){
fn.call(obj)
};
fnn();//2
fnn.call(window) // 2 硬綁定的函數不能再改變它的this綁定。
由于硬綁定比較常用,ES5提供了內置的方法:Function.prototype.bind,還有一種實現方式是
許多第三方庫以及許多內置的API的“上下文”,即內置函數的一些可選參數。
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()來實現顯式綁定的。
4、new綁定
這是第四條也是最后一條綁定規則,new綁定。
js所有的函數包括內置函數都可以用new調用,被稱為構造函數調用,其實js并不存在所謂的“構造函數”,只有對于函數的“構造調用”
發生構造調用,即用new來調用時,會自動執行以下操作:
1、創建一個全新的對象
2、這個新對象會被執行原型連接
3、這個新對象會被綁定到函數調用的this上
4、如果函數沒有返回其他對象,那么會自動返回這個新的對象
判斷this綁定
現在我們可以根據優先級來判斷函數在調用位置應用的是哪個規則:
1、如果函數是在new中調用,this綁定的是新創建的對象
2、如果函數通過apply()、call()或硬綁定,this綁定的是指定的對象
3、如果函數是在某個上下文對象中調用,this綁定的是這個上下文對象
4、如果都不是則使用默認綁定,非嚴格模式下this綁定全局對象,嚴格模式下綁定undefined
被忽略的this
如果你把null或者undefined作為this 的綁定對象傳入call()、apply()或bind()中,這些值在調用時會被忽略,實際使用的還是默認綁定規則。
ES6箭頭函數
之前介紹的四條規則可以應用所以的正常函數,但是ES6的箭頭函數是一種無法使用這些規則的特殊函數類型。它并不是function關鍵字定義的,并不使用這四條規則,而是由外層作用域來決定this。
實際上在ES6之前我們就已經在使用一種幾乎和箭頭函數完全一樣的模式,就是使用 self = this。從本質上來說,它們都是想要替代this機制。
this綁定的水還很深,這里只是介紹了一些粗淺的綁定規則知識,更深入的部分我也還沒學會! :)