Day 21:箭頭函數 (Arrow Functions) 的 this 和你想的不一樣 (1)
前面 Day15 ~ Day18 舉了很多例子來剖析傳統函數在各種情境下的 this
物件。
過程雖然眼花繚亂,但有一個大原則:看呼叫時的物件是誰。不是看定義的語彙位置,而是根據執行當下誰擁有這段程式碼,也就是看誰呼叫的。
但在 Arrow Functions 卻不是這麼回事,幾乎可以說是完全不同的另一套運作邏輯。
Day20 介紹箭頭函數 (Arrow Functions) 時示範了一個 this
的例子,可以發現和傳統函數的 this
運作原則大不相同。
這就是為什麼最好把 Arrow Functions 當成有別於傳統函數的新種族,在運作本質上他們有顯著的差別。
Arrow Functions 的 this
判斷原則
this
判斷原則那在 Arrow Functions 該怎麼判斷呢?
MDN & W3Schools:
In arrow functions, this retains the value of the enclosing lexical context's this.
Arrow functions do not have their own this.
In global code, it will be set to the global object.
Arrow Functions 的 this
和傳統函數的一個重大差異就是看的是語彙位置。
傳統函數每次呼叫函數,都會建立一個新的函數執行環境 (Function Execution Context),然後建立一個新的 this
引用物件,指向當下的呼叫者。
而 Arrow Functions 則不會有自己的 this
引用物件,呼叫 this
時,會沿用語彙環境外圍的 this
。
相信這樣還是很模糊,我們一樣用具體的例子來看。
下面會將前幾天探討傳統函數 this
的情境範例,換成 Arrow Functions 的狀況,並和傳統函數做比對。
傳統函數的部分由於前幾天的文章已經詳細解析過,可參考 Day15 ~ Day18 的文章,下面就不再重複細節,會很快帶過,著重在 Arrow Functions 部分的解析和對照。
1. 物件函式
1.1. 函數被定義在物件之內
傳統函數
傳統函數看的是誰呼叫,所以這個例子相對單純,player.whatsThis()
的呼叫者就是 player
:
Arrow Functions
whatsThis()
使用 Arrow Functions 定義,因此 whatsThis()
本身不會有自己的 this
,而是沿用外圍環境的 this
。
在這個例子裡,從 whatsThis()
再往外一層不在任何 Function Context 內,換言之就是 Global Context。在全域環境裡的 this
就是 Global 物件,在 HTML 裡就是 window
:
1.2. 借用函數 (函數被定義在物件之外)
傳統函數
傳統函數一樣單純看是誰呼叫,player.f()
的呼叫者就是 player
:
Arrow Functions
雖然是透過 player.f()
呼叫,但這邊看的是語彙位置,whatsThis()
再往外一層的 this
是 Global 物件:
1.3. 物件的屬性物件的函式
傳統函數
根據 obj.method()
的公式,呼叫者同樣很好辨認:
Arrow Functions
雖然呼叫者不同,函數定義的層次也不太一樣,但由於 player.f()
和 player.pet.f()
語彙位置再往外一層其實都是 Global Context,所以兩個函數回傳的 this
都是 Global 物件:
2. 簡易呼叫 (Simple Call)
2.1. 全域環境 (Global Context) 下定義函數 & 呼叫函數
傳統函數
前面沒有指定呼叫者的狀況,傳統函數在一般模式下是 Global 物件,嚴謹模式下是 undefined
:
Arrow Functions
由於看的是語彙位置,往外一層是 Global Context,不管在一般模式或嚴謹模式,this
都是 Global 物件:
2.2. 內部函數 (Inner Functions)
傳統函數
執行結果:
「Output#1」時,呼叫方式是
obj.f()
,因此this
是呼叫者obj
物件,this.x
是 20。「Output#2」時,呼叫方式是
foo()
,視同簡單呼叫,一般模式下this
是 Global 物件,因此this.x
是 10。
Arrow Functions I
如果只有內部函數是 Arrow Function,外部函數仍是傳統函數:
執行結果:
「Output#1」所在的函數仍是傳統函數,因此
this.x
不變仍是 20。「Output#2」所在的函數變成 Arrow Function,沿用外層的
this
,其外層就是obj.f()
,因此this.x
也是 20。
Arrow Functions II
如果外部函數是 Arrow Function,內部函數是傳統函數:
執行結果:
「Output#2」根據呼叫方式是
foo()
,視同簡單呼叫,一般模式下this
是 Global 物件,因此this.x
是 10。「Output#1」會沿用外層的
this
,往外找一層是 Global Context,所以this
也是 Global 物件。
Arrow Functions III
如果外部函數和內部函數都是 Arrow Function:
執行結果:
「Output#1」會沿用外層的
this
,往外找一層是 Global Context,所以this
也是 Global 物件。「Output#2」沿用外層的
this
,其外層是obj.f()
;而obj.f()
的this
如上面所說,經過沿用後是 Global 物件。
可以發現上面第 2 和 第 3 個情境的結果都是 Output#1 和 Output#2 等於 10。雖然他們最後呈現的結果碰巧一樣,但要注意背後的運作原理其實有所差別。
References
Last updated