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

閱讀時間約 7 分鐘

最近在上課時談到 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 關鍵字真的需要留神注意不同情境下的指向對象,我相信日後鐵定會遇到更多意想不到的驚喜,若有機會的話再撰文紀錄。

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

參考資料

16會員
34內容數
Bonjour à tous,我本身是法文系畢業,這邊會刊登純文組學習網頁開發的筆記。如果能鼓勵更多文組夥伴一起學習,那就太開心了~
留言0
查看全部
發表第一個留言支持創作者!
你可能也想看
JS 筆記 | Object 操作篇JavaScript Object 基礎操作筆記
Thumbnail
avatar
Jeremy Ho
2023-10-08
JS 筆記 | Array 操作篇JavaScript Array 基本操作筆記
Thumbnail
avatar
Jeremy Ho
2023-10-08
JS 學習筆記 #2 - 閉包 Closure在談到閉包前,要先談談範圍鏈,在 ES6 以前,變數透過 var 宣告,當時切分變數的最小單位為「函式」,有以下特性需要留意:內層可以取得外層的變數,但外層無法取得內層變數,在「定義」函式時就決定了範圍鏈,而非執行時
Thumbnail
avatar
傑米
2023-10-02
JS 學習筆記 #1 - 提升 Hoisting提升(Hoisting) 指的是在創造環境階段時就把變數準備好,這時值還沒被賦予到變數上。此類型的概念可以使用執行環境的「創造階段」與「執行階段」來理解。
Thumbnail
avatar
傑米
2023-08-16
JS 筆記 | Event LoopJavaScript event loop / asynchronous.
Thumbnail
avatar
Jeremy Ho
2023-08-09
六角學院-2022秋季 JS直播班心得分享因為九月的面試失利,踏上重新認識JS的旅程
Thumbnail
avatar
王聖禮
2022-12-09
【前端課程】六角學院JS直播班值回票價嗎?全勤實戰心得前陣子有讀者來信詢問我:「嗨!Vivian,我想要請問妳都是在哪裡學程式的呢?是實體課程嗎?」
Thumbnail
avatar
Vivian Yeh
2021-04-21
2021 六角學院 JS 工程師養成直播班心得 參加六角學院的 JS 工程師養成直播班,課程由洧杰老師擔任授課老師,總共有九週的線上直播課程以及課前知識補充的學習影片,與大家分享此次直播班的簡單心得
Thumbnail
avatar
karakamin
2021-04-19