2023-11-01|閱讀時間 ‧ 約 8 分鐘

【JS 學習筆記】箭頭函式與 this

最近在上課時談到 JavaScript ES6 所推出的箭頭函式 (arrow function),能讓開發者用更簡潔、優雅的語法使用函式,尤其是遇到 array.forEach()這種要把函式當參數的 method 更為方便。

然而美麗的事物總潛藏著危險:

箭頭函式與傳統函示的 this 綁定對象不同

這句話我聽過了好幾次,今天總算花了點時間釐清背後的意義,以此文章記錄。

不過在正式進入箭頭函式的 this 之前,我們還是要先熟悉一下 this、物件、函示之間的三角戀情。

簡談 JavaScript 的 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 物件了!

箭頭函式的 this (終於講到了......)

先直接說結論,根據 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 關鍵字真的需要留神注意不同情境下的指向對象,我相信日後鐵定會遇到更多意想不到的驚喜,若有機會的話再撰文紀錄。

希望這篇踩地雷的筆記能夠幫助到其他學習者~

參考資料

分享至
成為作者繼續創作的動力吧!
Bonjour à tous,我本身是法文系畢業,這邊會刊登純文組學習網頁開發的筆記。如果能鼓勵更多文組夥伴一起學習,那就太開心了~
© 2024 vocus All rights reserved.