最近在上課時談到 JavaScript ES6 所推出的箭頭函式 (arrow function),能讓開發者用更簡潔、優雅的語法使用函式,尤其是遇到 array.forEach()
這種要把函式當參數的 method 更為方便。
然而美麗的事物總潛藏著危險:
箭頭函式與傳統函示的 this 綁定對象不同。
這句話我聽過了好幾次,今天總算花了點時間釐清背後的意義,以此文章記錄。
不過在正式進入箭頭函式的 this
之前,我們還是要先熟悉一下 this
、物件、函示之間的三角戀情。
關鍵字 this
披著單純可愛的羊皮,實質上折磨了成千上萬的 JavaScript 學習者 (當然包含我自己)。原因在於 this
所綁定的對話,會隨著程式碼情境不同而改變,可謂名符其實的政治變色龍。
簡單來說,this
這個變數會在函示被宣告後自動產生。至於 this
的指認對象要端看呼叫它的物件而定。我們用程式碼來看會更清楚:
let user = {
name: "Mimiball",
age: 30,
greet: function () {
console.log(this); // the user object
console.log(`Hey! This is ${this.name}`);
}
};
user.greet(); // Hey! This is Mimiball
程式碼第九行,物件 user 呼叫了 greet 函式,所以 this
關鍵字指向了 user 物件,才會把物件中的名字列印出來。
那如果直接在 global 的環境中把 this
列印出來會是什麼樣子呢?
window
物件除非有綁定到其他對象,否則 this
的預設指向都是 window
物件。所以若我們直接跑這行程式碼,console 會列印出 window
物件:
console.log(this); // Window {...}
我會想像有個名為 global 的上帝函示,把其他程式碼都包覆在裡面,感受上天滿滿的愛,所以下面的程式碼會得出和上方案例一樣的結果:
function global() {
console.log(this);
}
window.global(); // Window {...}
this
我們在上面已經談過 this 會指向呼叫函示的物件,因此下方程式碼會列印出 croissant 這個物件。
let croissant = {
name: "object",
print: function() {
console.log(this);
}
}
croissant.print(); // croissant obj
那如果今天把 croissant.print()
當成值賦予給變數再呼叫呢?
let croissant = {
name: "object",
print: function() {
console.log(this);
}
}
let printer = croissant.print();
printer(); // Window {...}
我當下的直覺是 croissant 物件,但結果大錯特錯!由於 printer 直接被呼叫 (前面沒有物件),所以等於 window.printer()
,既然沒有物件呼叫,那就由上帝物件來接手吧。
this
藏在物件當中的物件呢......單純用文字敘述很像在繞口令,請參考程式碼:
let croissant = {
name: "object",
print: function() {
console.log(this)
function printAgain() {
console.log(this)
}
printAgain()
}
}
croissant.print();
第一層 croissant.print()
的結果為 croissant 物件,這沒什麼太大問題,但第二層 printAgain()
的呼叫結果......是 window
物件 🫠
這是因為 printAgain()
並非直接由 croissant 物件呼叫,所以往上找回了預設的 window
物件。
可、可是我就希望讓第二層函示的 this
指向到 croissant 物件啊,那該怎麼辦?
這就是箭頭函式登場的時候了。原先我們使用傳統的寫法宣告 printAgain()
,現在我們改用箭頭函式的寫法。
const croissant = {
name: "object",
print: function() {
console.log(this)
const printAgain = () => {
console.log(this)
}
printAgain()
}
}
croissant.print();
阿呀,太神奇了,怎麼換成箭頭函式,printAgain()
列印出來的結果也變成 croissant 物件了!
先直接說結論,根據 MDN,箭頭函式沒有自己的 this
綁定,換句話說,箭頭函式和一般函式不同,不會自動產生 this
。
Arrow functions don't have their own bindings to this, arguments, or super, and should not be used as methods.
既然如此,箭頭函式中的 this
會指向該函示被宣告之前的綁定對象,也就是會繼承父層的作用域。在上述的範例中,父層作用域即為 window
物件。
那如果我們把 print()
也改寫成箭頭函式呢?
const croissant = {
name: "object",
print: () => {
console.log(this)
const printAgain = () => {
console.log(this)
}
printAgain()
}
}
croissant.print();
沒錯,改成這樣的話,print()
和 printAgain()
都會指向 window
物件。
this
關鍵字真的需要留神注意不同情境下的指向對象,我相信日後鐵定會遇到更多意想不到的驚喜,若有機會的話再撰文紀錄。
希望這篇踩地雷的筆記能夠幫助到其他學習者~