箭頭函式(Arrow function)的 this 究竟是什麼?如何簡單理解 Lexical "this"?

更新 發佈閱讀 9 分鐘
raw-image

在傳統開發的過程中,很容易會搞混一般的 this 和箭頭函式(arrow function)中的 lexcial "this" 兩者的差異。在傳統函式中,this 是在函式調用時動態決定,換句話說只要是哪一個物件呼叫函式,他就會抓取該物件進行呼叫;而箭頭函式則是在定義時就決定,並繼承自父層的作用域。


傳統的 this 定義

我們直接以下列的案例來說明。我們實例化一個物件 person,並設定一個 greet 的方法,其中使用 SetTimeout 的方法來執行。

const person = {
name: 'Chris',
greet: function() {
setTimeout(function() {
console.log(`Hello, my name is ${this.name}`);
}, 1000);
}
};

person.greet(); // Hello, my name is undefined (after 1 second)

在上面的這個例子中,因為 setTimeout 並非 JavaScript 內建的函式,而是屬於 Web API,因此在執行 SetTimeout 後會先將 Timer 放進 Web API 等待呼叫。

這時就會先在 Web API 確定是否一秒後,確認一秒後會執行 Web API 的 Timeout,將 Timeout 放進 task Quene 移到 Stack 中執行。此時在全域環境(Stack)確認後,並沒有待執行的內容。

接著 Timeout 函式就會被放進 Stack 中,Timeout 就會呼叫 Callback 來執行。此時,執行的環境就會是 window 而非此處的 person。因此結果就會是 "Hello, my name is undefined"。

除非特別使用 Call 或 Apply 來綁定呼叫的物件,才能讓這個 greet 順利運行。以下是實際範例:

const person = {
name: 'Chris',
greet: function() {
setTimeout(function() {
console.log(`Hello, my name is ${this.name}`);
}.apply(this), 1000);
}
};
person.greet(); // Hello, my name is Chris (after 1 second)

透過 Apply 強制將此處的 this(就是 person)綁定給 setTimeout 的 callback function,才在執行 greet 時,順利顯示正確的 Chris。


箭頭函式中的 lexcial this 定義

當初在理解父層 this 的環境定義時,時常不確定指的到底是哪一個環境。但這裡只要簡單的記得:

箭頭函式的 this 指的是定義該 function 的環境(lexical Scope)中,所使用的 this。

我們一樣以上面的例子說明:

const person = {
name: 'Chris',
greet: function() {
setTimeout(() => {
console.log(`Hello, my name is ${this.name}`);
}, 1000);
}
};

person.greet(); // Hello, my name is Chris

雖然上述的 setTimeout 創建了一個執行環境(Execution context),但因為箭頭函式的 this 是 lexical bound。

可以看到此處創建箭頭函式的環境,是被宣告在 greet 這個 function 裡,所以 greet 就是這個箭頭函式的 Lexical Scope。因此此處 greet 的 this,就是箭頭函式的 this。因此,此處仍然會顯示 Chris。

這裡也舉另一個例子,如果 greet 不要使用一個 function 包裹,那上層的 greet 裡使用 this 一樣會抓到 window。因為在定義的當下,再往上一層的 Scope Chain 會抓到 window,因此此處的 this 就會是 window。

因此若要在方法使用箭頭函式,則需使用傳統函式包裹,才能順利使用。

如何理解執行上下文(Execution Context)?

一般來說,總共有五中 Execution Context 會生成:

  1. 全域執行上下文(Global Execution Context):全域執行上下文是在程式開始執行時創建的,代表整個程式的執行環境。在全域執行上下文中,會創建全域物件(global object)和全域範疇(global scope)。
  2. 函式執行上下文(Function Execution Context):每當函式被呼叫時,都會創建一個新的函式執行上下文。每個函式執行上下文都有自己的範疇,包含函式內部的變數、參數、函式內部定義的其他函式等。
  3. Eval 函式執行上下文(Eval Function Execution Context):當程式碼被 eval() 函式執行時,會創建一個 Eval 函式執行上下文。Eval 函式執行上下文允許執行字串中的程式碼,但在現代 JavaScript 中,建議盡量避免使用 eval() 函式。
  4. 模組執行上下文(Module Execution Context):在使用 ES6 模組的情況下,每個模組都會有自己的模組執行上下文,模組內的變數、函式等僅在模組內可見,預設不會暴露到全域範疇。
  5. 物件執行上下文(Object Execution Context):當函式作為物件的方法被呼叫時,該函式執行上下文被稱為物件執行上下文。在物件執行上下文中,物件本身被設置為 this,並且可以訪問該物件的屬性和方法。

在理解箭頭函式的 this 時,最容易被搞混的原因,其實是來自於上述的「物件執行上下文」與「函式執行上下文」,兩者的認識不夠清楚。

箭頭函式的 this 如何參考?

一般來說,當一個傳統函式作為某物件的執行方法時,就會創建物件執行上下文。因此,在方法中使用 this,就會指向該物件本身。

然而,將箭頭函式使用在物件方法時,因為本身箭頭函式不會建立物件執行上下文,因為他本身並沒有 this。相反地,箭頭函式僅參考外層範疇(lexical Scope)環境中的 this、argument。除此之外,箭頭函式也沒有自己的建構函式。

換句話說,如果直接在全域中物件呼叫 {key: this},該 this 會指向全域物件,因為此處的 this 仍然屬於全域執行上下文,因此 this 依然會顯示 window,或是嚴格模式下的 undefined。

因此,只要簡單瞭解:

箭頭函式本身並不會建立物件上下文,而是僅會參考外層環境(Lexical Scope)的 this,建立函式上下文而已。

瞭解 this 在 JavaScript 中扮演的角色

原先 this 綁定執行的物件,是為了呼叫時的方便;但隨著開發的複雜、物件繼承的情境,甚至非同步的問題變得複雜,也因此才出現了箭頭函式。

但箭頭函式並非要取代傳統函式,因為箭頭函式本身並沒有自己的 this 可以使用,單純是綁定外層的父環境,因此在 JavaScript 後續開發的過程,主要扮演了互補的角色。



參考資料

留言
avatar-img
學.誌|Chris Kang的沙龍
7.5K會員
14內容數
此處作為整理前端(Frontend)和相關的 HTML、CSS、JavaScript、React 等前端觀念與技巧,全部都會收錄在這個專題之中。同時也會將相關的技術與反思記錄在此,歡迎各位讀者互相交流。
2024/04/08
本文帶你深入探索 TypeScript 中的 satisfies 特性,能幫助你實現精確的型別推導與型別檢查。透過實際案例,展示如何使用 satisfies 提升代碼的型別安全與程式碼的整潔,是每位 TypeScript 開發者不可或缺的知識。
Thumbnail
2024/04/08
本文帶你深入探索 TypeScript 中的 satisfies 特性,能幫助你實現精確的型別推導與型別檢查。透過實際案例,展示如何使用 satisfies 提升代碼的型別安全與程式碼的整潔,是每位 TypeScript 開發者不可或缺的知識。
Thumbnail
2024/03/26
本文介紹 TypeScript 常遇到的混合型別,以及如何透過五種型別防禦(Type Guard)來解決。涵蓋了使用型別斷言、型別謂詞、in 運算子、typeof 運算子以及 instanceof 運算子這幾種方式。透過本文的學習,能夠更好地運用 TypeScript 進行程式碼開發。
Thumbnail
2024/03/26
本文介紹 TypeScript 常遇到的混合型別,以及如何透過五種型別防禦(Type Guard)來解決。涵蓋了使用型別斷言、型別謂詞、in 運算子、typeof 運算子以及 instanceof 運算子這幾種方式。透過本文的學習,能夠更好地運用 TypeScript 進行程式碼開發。
Thumbnail
2024/02/04
本文將深入探討鏈表的核心概念,使用 JavaScript 來說明如何實現和操作鏈表(Linked List),包括 append、prepend、remove、find 和 reverse 等五大方法。
Thumbnail
2024/02/04
本文將深入探討鏈表的核心概念,使用 JavaScript 來說明如何實現和操作鏈表(Linked List),包括 append、prepend、remove、find 和 reverse 等五大方法。
Thumbnail
看更多
你可能也想看
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
在 vocus 與你一起探索內容、發掘靈感的路上,我們又將啟動新的冒險——vocus App 正式推出! 現在起,你可以在 iOS App Store 下載全新上架的 vocus App。 無論是在通勤路上、日常空檔,或一天結束後的放鬆時刻,都能自在沈浸在內容宇宙中。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
vocus 慶祝推出 App,舉辦 2026 全站慶。推出精選內容與數位商品折扣,訂單免費與紅包抽獎、新註冊會員專屬活動、Boba Boost 贊助抽紅包,以及全站徵文,並邀請你一起來回顧過去的一年, vocus 與創作者共同留下了哪些精彩創作。
Thumbnail
這篇內容,將會講解什麼是腳本函式,以及與腳本函式相關的知識。包括腳本的簡介、使用函式(或全域變數)的注意事項、定義全域變數、定義函式、什麼是宣告、局部變數的應用。
Thumbnail
這篇內容,將會講解什麼是腳本函式,以及與腳本函式相關的知識。包括腳本的簡介、使用函式(或全域變數)的注意事項、定義全域變數、定義函式、什麼是宣告、局部變數的應用。
Thumbnail
立即(調用)函式 (簡稱 IIFE,Immediately Invoked Function Expression) 是種在定義完可以馬上執行的函式表達式。
Thumbnail
立即(調用)函式 (簡稱 IIFE,Immediately Invoked Function Expression) 是種在定義完可以馬上執行的函式表達式。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
  當我們要使用執行緒的時候,就要引用System.Threading的類別庫,程式才可以使用唷!這篇內容先說明:建立與啟用執行緒、跨執行緒控制UI介面、等待或暫停時間執行緒、共享資源的部分。
Thumbnail
  當我們要使用執行緒的時候,就要引用System.Threading的類別庫,程式才可以使用唷!這篇內容先說明:建立與啟用執行緒、跨執行緒控制UI介面、等待或暫停時間執行緒、共享資源的部分。
Thumbnail
在傳統開發的過程中,很容易會搞混一般的 this 和箭頭函式(arrow function)中的 lexcial "this" 兩者的差異。本文就以實際的例子來說明各自的差異,以及在未來使用時需要注意哪一些細節。
Thumbnail
在傳統開發的過程中,很容易會搞混一般的 this 和箭頭函式(arrow function)中的 lexcial "this" 兩者的差異。本文就以實際的例子來說明各自的差異,以及在未來使用時需要注意哪一些細節。
Thumbnail
在JS中很重要的觀念就是hoisting,中文叫做「提升」
Thumbnail
在JS中很重要的觀念就是hoisting,中文叫做「提升」
Thumbnail
執行以上程式碼,然後看到了這個結果: 為什麼「延遲0秒」的函式寫在上方,但在console印出的結果,它還是被排在第二順位? 利用AC教材提供的youtube演講連結一窺究竟: 演講提供了更多JS的細節概念,身為JS新手的我還在消化,但若針對教案提出的問題來回答,加上利用google大神查到MDN的
Thumbnail
執行以上程式碼,然後看到了這個結果: 為什麼「延遲0秒」的函式寫在上方,但在console印出的結果,它還是被排在第二順位? 利用AC教材提供的youtube演講連結一窺究竟: 演講提供了更多JS的細節概念,身為JS新手的我還在消化,但若針對教案提出的問題來回答,加上利用google大神查到MDN的
Thumbnail
前言 這是第一次寫技術文章,但其實應該也只能說是蒐集很多資料並學習如何透過自己的話解釋的內容,並不能像其他大神可能分享一些很酷的技術,目標就單純是為了完成最後一週的作業(如下)。 走入非同步之前 執行環境(Execution Context) 執行環境堆疊 (Execution stack)
Thumbnail
前言 這是第一次寫技術文章,但其實應該也只能說是蒐集很多資料並學習如何透過自己的話解釋的內容,並不能像其他大神可能分享一些很酷的技術,目標就單純是為了完成最後一週的作業(如下)。 走入非同步之前 執行環境(Execution Context) 執行環境堆疊 (Execution stack)
Thumbnail
int main()、註解//、include 、命名空間、using namespace
Thumbnail
int main()、註解//、include 、命名空間、using namespace
Thumbnail
this 是 JavaScript 的一個關鍵字,也是讓新手困擾許久的主題,今天讓我們用更簡單、直接的方式來了解 this。
Thumbnail
this 是 JavaScript 的一個關鍵字,也是讓新手困擾許久的主題,今天讓我們用更簡單、直接的方式來了解 this。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News