this在物件導向程式設計中非常重要,也是javascript中最複雜的機制之一,它會被自動定義在所有函式(箭頭函式除外)的作用域中,它實際上是在函式被呼叫時發生繫結,它的繫結和函式宣告的位置沒有任何關係,只取決於函式的呼叫方式,本文將基於this的四種呼叫方式來了解它的繫結機制。
- 函式呼叫
- 物件方法呼叫
- new呼叫
- call、apply、bind呼叫
一、函式呼叫
this的繫結規則完全取決於呼叫位置,如果獨立函式在全域性進行呼叫,那麼this會被繫結到全域性物件。
function run(){
console.log(this.a)
}
var a = 2;
run(); // 2
複製程式碼
我們知道宣告在全域性作用域中的變數a就是全域性物件(window)的一個同名屬性,當我們呼叫run()時,this指向了全域性物件(window),所以得到了結果:2。
注意:只有在非嚴格模式下,this才會繫結到全域性物件,嚴格模式下會被繫結到undefined。
function run(){
console.log(this === window);
}
run(); // true
複製程式碼
二、物件方法呼叫
當一個函式儲存為物件的屬性時,我們稱它為一個方法,當一個方法被呼叫時,this會被繫結到該物件。
var myObject = {
name:'張三',
run:function(){
console.log('我叫'+this.name);
}
}
myObject.run(); // 我叫張三
複製程式碼
從上面的例子可以看到通過this可以取得所屬物件的上下文。
三、new呼叫
在javascript中,有一個new關鍵字,通過new關鍵字呼叫的函式,我們稱之為建構函式,當使用new呼叫函式的時候會自動執行下面的操作:
- 建立一個全新的物件
- 這個新物件會執行原型連線。
- 這個新物件會繫結到函式呼叫的this
- 如果函式沒有返回其他物件,那麼new關鍵字中的函式呼叫會自動返回這個新物件
function Foo(a) {
this.a = a;
}
var bar = new Foo(2);
console.log( bar.a ); // 2
複製程式碼
使用new來呼叫Foo(..) 時,我們會構造一個新物件並把它繫結到Foo(..)呼叫中的 this上。
四、call、apply、bind呼叫
上面介紹的三種this繫結機制,都是javascript預設的隱式繫結,那麼如果我們不想在物件內部包含函式或者使用new關鍵字呼叫,而想在某個物件上強制呼叫函式,該怎麼做呢?我們都知道所有函式(箭頭函式除外)都可以使用 call(..) 和 apply(..) 及bind(...)方法,它們的第一個引數是一個物件,它們會把這個物件繫結到 this,接著在呼叫函式時指定這個 this。因為可以直接指定 this 的繫結物件,因此稱之為顯式繫結。
注意:從 this 繫結的角度來說,call(..)和apply(..)、bind是一樣的,call和apply第二個引數分別是引數列表和陣列,bind和call、apply不同的是:在於bind方法返回值是函式。
function foo(){
console.log(this.a)
}
var myObject = {
a:15,
};
foo.call(myObject); // 15
複製程式碼
上面的程式碼通過 foo.call(..),我們可以在呼叫 foo 時強制把它的 this 繫結到 myObject 上,通過這些方法,我們也很容易模擬出一個new繫結,上面一節我們提到了new繫結的執行過程,現在我們來實現一個。
function newCreate(fn){
// 建立一個全新的物件
var newObj = new Object();
// 新物件會執行原型連線到函式原型
newObj.__proto__ = fn.protype;
// 將 arguments 物件轉為陣列,並把fn函式從引數列表中移除
var args = [].slice.call(arguments,1);
// 新物件繫結到函式呼叫的this
var result = fn.apply(newObj,args);
// 如果函式沒有返回其他物件,那麼new關鍵字中的函式呼叫會自動返回這個新物件
if(Object.prototype.toString.call(result) == "[object Object]" ) {
return result
} else {
return newObj;
}
}
複製程式碼
小結
如果要判斷一個執行中函式的this繫結,就需要找到這個函式的直接呼叫位置。找到之後就可以順序應用下面這四條規則來判斷 this 的繫結物件。
- 由new呼叫?繫結到新建立的物件。
- 由call或者apply(或者bind)呼叫?繫結到指定的物件。
- 由上下文物件方法呼叫?繫結到那個上下文物件。
- 預設(獨立函式呼叫):在嚴格模式下繫結到undefined,否則繫結到全域性物件。
當然ES6中的箭頭函式不適用上述規則,因為箭頭函式會繼承外層非箭頭函式的 this繫結。