this繫結機制及特性

crayons發表於2019-12-18

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 的繫結物件。

  1. 由new呼叫?繫結到新建立的物件。
  2. 由call或者apply(或者bind)呼叫?繫結到指定的物件。
  3. 由上下文物件方法呼叫?繫結到那個上下文物件。
  4. 預設(獨立函式呼叫):在嚴格模式下繫結到undefined,否則繫結到全域性物件。

當然ES6中的箭頭函式不適用上述規則,因為箭頭函式會繼承外層非箭頭函式的 this繫結。

相關文章