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

更新於 發佈於 閱讀時間約 8 分鐘
Photo by Juanjo Jaramillo on Unsplash
在傳統開發的過程中,很容易會搞混一般的 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 後續開發的過程,主要扮演了互補的角色。

參考資料

為什麼會看到廣告
此處作為整理前端(Frontend)和相關的 HTML、CSS、JavaScript、React 等前端觀念與技巧,全部都會收錄在這個專題之中。同時也會將相關的技術與反思記錄在此,歡迎各位讀者互相交流。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
你可能也想看
Google News 追蹤
Thumbnail
2025 年,從分享精彩的 #Myvocus2024 年度回顧開始! #Myvocus2024 年度回顧通知已送達 vocus 的 2024 有超過 12 萬筆訂單、35 萬則以上的內容、16 萬以上的新會員、4 千+ 筆數位商品訂單,5 萬 + 則貼文! 曬曬你的 2024 vocus 吧!
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
這篇內容,將會講解什麼是腳本函式,以及與腳本函式相關的知識。包括腳本的簡介、使用函式(或全域變數)的注意事項、定義全域變數、定義函式、什麼是宣告、局部變數的應用。
主要來講宣告函式跟箭頭函式 : 宣告函式(Function Declaration) 語法: function functionName(parameters) { return result; } 特點: 使用 function 關鍵字 函式名稱是必需的 存在函式
就是指變數可以被訪問和使用的範圍,來說一下var、let和const的作用域差異。 var :function example() { console.log(x); // 輸出: undefined 因為變量提升造成的 var x = 5; } 函數作用域或全域作用域 可以重複宣告
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
※ Promise基本介紹 什麼是 Promise? Promise 是 JavaScript 的一個構造函式,用於創建表示非同步操作的物件實例。使用 new Promise() 時,你會創建一個包含非同步操作的實例,這個實例可以透過其繼承的方法如 then(), catch(), 和 fina
※ 函式進階介紹: 箭頭函式: 箭頭函式相比於一般函式,語法相當簡潔。除了少去 function 關鍵字,如果只有一個參數,箭頭函式可以省略括號;只有一行程式碼,就是直接簡單返回一個變數或簡單的表達式,可以省略大括號和 return。例子如下: //一般函式計算平方用的寫法 const squ
※ 函式基礎介紹: ※ JavaScript 特殊的函式特性: 函式可以當成值來傳遞 (可以放進變數或放進物件) 函式可以當成函式的參數 callback - 在特定事件中觸發函式 (非同步特性) ※ 函式的基本寫法: ※ 調用 (invoke) 函式: "調用" 意指呼叫或執行
Thumbnail
立即(調用)函式 (簡稱 IIFE,Immediately Invoked Function Expression) 是種在定義完可以馬上執行的函式表達式。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
Thumbnail
2025 年,從分享精彩的 #Myvocus2024 年度回顧開始! #Myvocus2024 年度回顧通知已送達 vocus 的 2024 有超過 12 萬筆訂單、35 萬則以上的內容、16 萬以上的新會員、4 千+ 筆數位商品訂單,5 萬 + 則貼文! 曬曬你的 2024 vocus 吧!
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
這篇內容,將會講解什麼是腳本函式,以及與腳本函式相關的知識。包括腳本的簡介、使用函式(或全域變數)的注意事項、定義全域變數、定義函式、什麼是宣告、局部變數的應用。
主要來講宣告函式跟箭頭函式 : 宣告函式(Function Declaration) 語法: function functionName(parameters) { return result; } 特點: 使用 function 關鍵字 函式名稱是必需的 存在函式
就是指變數可以被訪問和使用的範圍,來說一下var、let和const的作用域差異。 var :function example() { console.log(x); // 輸出: undefined 因為變量提升造成的 var x = 5; } 函數作用域或全域作用域 可以重複宣告
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
※ Promise基本介紹 什麼是 Promise? Promise 是 JavaScript 的一個構造函式,用於創建表示非同步操作的物件實例。使用 new Promise() 時,你會創建一個包含非同步操作的實例,這個實例可以透過其繼承的方法如 then(), catch(), 和 fina
※ 函式進階介紹: 箭頭函式: 箭頭函式相比於一般函式,語法相當簡潔。除了少去 function 關鍵字,如果只有一個參數,箭頭函式可以省略括號;只有一行程式碼,就是直接簡單返回一個變數或簡單的表達式,可以省略大括號和 return。例子如下: //一般函式計算平方用的寫法 const squ
※ 函式基礎介紹: ※ JavaScript 特殊的函式特性: 函式可以當成值來傳遞 (可以放進變數或放進物件) 函式可以當成函式的參數 callback - 在特定事件中觸發函式 (非同步特性) ※ 函式的基本寫法: ※ 調用 (invoke) 函式: "調用" 意指呼叫或執行
Thumbnail
立即(調用)函式 (簡稱 IIFE,Immediately Invoked Function Expression) 是種在定義完可以馬上執行的函式表達式。
Thumbnail
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。