JS 原力覺醒 Day17 - this 的四種繫結

今天要談到的是 JS 裡面最常被提出來討論的部分,也就是 this 的指向,前面有提到當全域執行環境被產生出來之後,除了全域物件 window ,一個指向這個 window 物件的 this 也會跟著被產生。所以接下來你就可以用 this 來指稱 window 物件,除此之外, this 並不永遠都指向 window 物件,根據不同的呼叫方式,this 所指向的值也會不一樣,所以,你「如何呼叫」這件事情就會很大一部分影響 this 的指向。

Outline

  • Javascript 裡面的 this
  • 預設繫結 (Default Binding)
  • 隱含的繫結
  • 明確的繫結(Explicit Binding
  • new 繫結
  • this 繫結的優先順序
  • 參考書目

Javascript 裡面的 this

在正式進入 this 解說之前,我們先來了解一下為什麼 this 這麼重要, this 讓我們可以很方便地從執行環境內部取得外部物件,用另一個方式說就是,this 可以讓我們在呼叫函式時們決定要指向哪一個物件。

不過如果沒有好好使用的話,就會出現 this 指向錯誤的物件之類的不如預期的情況出現,所以我們在使用之前,一定要先了解 this 檯面下的運作方式。

四種繫結 ( Binding )

所謂 this 的繫結指的是指向哪一個物件, this 大致上一共有四種繫結,讓我們一個一個來看看:

1. 預設繫結 ( Default Binding )

預設繫結:foo 的 this 被 bind 到全域物件Window底下,這是最常見也最好理解的繫結。

	function foo(){
         console.log(this.a); 
  }
  
  var a=2; 
  
  foo() //2;

2. 隱含的繫結

隱含繫結:隱含的指出 this 綁定的對象,使用 . 可以取用到物件底下的屬性,同時也在告訴 JS this 的指向:

var foo = {
     a:'I am in foo',     
     bar:function(){
	     console.log(this.a); 
     }, 
 } 
 
 foo.bar() //I am in foo;

繫結的失去 ( 繫結在賦值時會失效 )

當你用隱含的繫結去呼叫物件內的函式時, this 會正確的指向該物件,但是一但你將這個函式指派給另外一個變數時,這個變數就只會參考到該函式,而不是擁有該函式的整個物件,這個時候再去執行的時候, this 就會因為找不到該物件而指向全域,這個現象就稱為隱含繫結的失去:

    var obj = {
       a:'obj a',
       foo: function foo(){
         console.log(this.a);
	     },
   }

   var bar = obj.foo;

   var a = ' global a'; //Something Happened. 

   bar(); // global a

3. 明確的繫結(Explicit Binding)

在JS 裡面,函是可以使用 call()、apply(),來指定綁定物件的 thiscallapply 在使用上兩個還蠻相像的,只差在參數傳入的方式,第一個參數都是指定 this 指向的物件, 而第二個以後的參數則是要傳入該函式的參數,apply 是以陣列的方式來決定傳入函式的參數順序,而 call 則是直接以第二個參數後的數量及順序來決定:

function foo(arg1,arg2){
       console.log(this.a);   
}
var obj ={
    a:2,
} 

foo() // undefined
foo.call(obj , arg1 , arg2);//2  
foo.apply(obj,[arg1,arg2]);//2  

//call 跟 apply 基本上行為相同,只差在參數傳入的方式不同

硬繫結 - ( Hard binding )

Hard Bind 是明確繫結的一種變化.可以確保某個 function 的 this 每次被呼叫的時候都與目標物件綁定,可以看到因為多包一層function的關係,即時bar在怎麼用call指定this環境,裡面的主要function :foo.call(obj)依然不會受到影響。

function foo(){
            console.log(this.a); 
        }
        
        var obj = {
            a:2
        }
        
        var bar = function(){
            console.log('this= '+this);  
            foo.call(obj); 
        }
         
        bar();            //this= [object Window]
                           //2
        bar.call(window);  //this= [object Window]
                           //2

4. new 繫結

當一個函式被以 new 的方式呼叫時,神奇的事情發生了:

  1. 會有一個全新的物件被創造出來
  2. 這個新建構的物件帶有 prototype 連結 (先不討論)
  3. 這個新建構的物件會被設為那個函示呼叫的 this
  4. 除非該函式提供了自己的替代物件,不然這個以new調用的函式呼叫會自動回傳這個新建構的物件。

函式搭配 new 關鍵字來創造物件的方式,也是早期物件導向宣告新物件的方式,而後來 class 關鍵字的出現,也讓我們用更直觀的方式宣告物件,因此像這樣使用 function 創造物件的方式也就比較不常見了。

function foo(){
    this.a=2; 
}

var bar = new foo(); 

//{}
//this = {}
//this.a=2 
//{a:2}
//return {a:2}
//bar.a=2

console.log(bar.a); //2

this 繫結的優先順序

當 this 的繫結重複的時候,會以下面的優先順序決定採用哪一種繫結:

預設 < 隱含 < 明確繫結 < new 繫節

參考書目

本篇文章參考 You Dont Know JS 系列的 Scope & Closure

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×