今天要談到的是 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(),來指定綁定物件的 this
,call
與 apply
在使用上兩個還蠻相像的,只差在參數傳入的方式,第一個參數都是指定 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 的方式呼叫時,神奇的事情發生了:
- 會有一個全新的物件被創造出來
- 這個新建構的物件帶有 prototype 連結 (先不討論)
- 這個新建構的物件會被設為那個函示呼叫的 this
- 除非該函式提供了自己的替代物件,不然這個以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