自 ES6 問世以來,箭頭函數(Arrow Functions)以其簡潔的語法和對 this 的詞法綁定,迅速成為了 JavaScript 開發者的“新寵”。我們似乎傾向於在任何可以使用函數的地方都換上 () => {}

然而,箭頭函數並非“銀彈”,它並不能完全替代傳統的 function 關鍵字。過度濫用箭頭函數,尤其是在不理解其工作原理的情況下,會導致難以追蹤的 bug 和意外行為。this 的指向是 JavaScript 中最核心也最容易混淆的概念之一,而箭頭函數和傳統 function 在 this 的處理上有着本質區別。

核心區別速記:

  • functionthis 的值是在函數被調用時動態決定的,取決於誰調用了它。
  • => (箭頭函數): 它沒有自己的 this。它會捕獲其定義時所在上下文的 this 值,這個綁定是固定的,不會改變。

理解了這一點,我們就會明白為什麼在以下 5 個場景中,堅持使用 function 不僅是最佳實踐,甚至是唯一的正確選擇。

場景一:對象的方法 (Object Methods)

這是最經典、最常見的場景。當我們為一個對象定義方法時,通常希望 this 指向該對象本身,以便訪問其屬性。

❌ 錯誤示範 (使用箭頭函數):

const person = {
  name: '老王',
  age: 30,
  sayHi: () => {
    // 這裏的 this 繼承自全局作用域 (在瀏覽器中是 window),而不是 person 對象
    console.log(`大家好,我是 ${this.name}`);
  }
};

person.sayHi(); // 輸出: "大家好,我是 " (或者 "大家好,我是 undefined")

在這個例子中,箭頭函數 sayHi 在 person 對象中定義,但它的 this 捕獲的是定義 person 對象時的上下文,即全局作用域。全局作用域下沒有 name 屬性,所以結果不是我們想要的。

✅ 正確姿勢 (使用 function):

const person = {
 name: '老王',
 age: 30,
 sayHi: function() {
    // 這裏的 this 在調用時被動態綁定為 person 對象
    console.log(`大家好,我是 ${this.name}`);
  },
 // ES6 對象方法簡寫形式,本質上也是一個 function
 sayHiShorthand() {
    console.log(`大家好,我是 ${this.name}`);
  }
};

person.sayHi(); // 輸出: "大家好,我是 老王"
person.sayHiShorthand(); // 輸出: "大家好,我是 老王"

結論: 當我們為對象定義一個需要引用該對象自身屬性的方法時,請使用 function 或 ES6 方法簡寫。

場景二:DOM 事件監聽器 (Event Listeners)

在使用 addEventListener 為 DOM 元素綁定事件時,我們常常需要訪問觸發該事件的元素本身(例如,修改它的樣式、內容等)。傳統 function 會自動將 this 綁定到該 DOM 元素上。

❌ 錯誤示範 (使用箭頭函數):

const button = document.getElementById('myButton');

button.addEventListener('click', () => {
  // 這裏的 this 依然是 window 或 undefined,而不是 button 元素
  this.classList.toggle('active'); // TypeError: Cannot read properties of undefined (reading 'classList')
});

箭頭函數再次從外部作用域捕獲 this,導致我們無法直接操作點擊的按鈕。

✅ 正確姿勢 (使用 function):

const button = document.getElementById('myButton');

button.addEventListener('click', function() {
  // 在這裏,this 被正確地綁定為觸發事件的 button 元素
  console.log(this); // <button id="myButton">...</button>
  this.classList.toggle('active'); // 正常工作
});

結論: 在 DOM 事件監聽回調中,如果我們需要用 this 來引用觸發事件的元素,請使用 function

場景三:構造函數 (Constructor Functions)

箭頭函數在設計上就不能作為構造函數使用。如果我們嘗試用 new 關鍵字來調用一個箭頭函數,JavaScript 會直接拋出錯誤。這是因為構造函數需要有自己的 this 來指向新創建的實例,並且需要一個 prototype 屬性,而箭頭函數兩者都不具備。

❌ 錯誤示範 (使用箭頭函數):

停止濫用箭頭函數:這5個場景請務必使用 function_構造函數


✅ 正確姿勢 (使用 function 或 class):

停止濫用箭頭函數:這5個場景請務必使用 function_ES6_02


結論: 永遠不要用箭頭函數作為構造函數。請使用 function 或 class

場景四:原型方法 (Prototype Methods)

與對象方法類似,當我們為構造函數的原型 prototype 添加方法時,我們也希望 this 指向調用該方法的實例。

❌ 錯誤示範 (使用箭頭函數):

停止濫用箭頭函數:這5個場景請務必使用 function_作用域_03


✅ 正確姿舍 (使用 function):

停止濫用箭頭函數:這5個場景請務必使用 function_作用域_04


結論: 在 prototype 上定義方法時,請使用 function,以確保 this 指向類的實例。

場景五:需要 arguments 對象的函數

箭頭函數沒有自己的 arguments 對象。arguments 是一個類數組對象,包含了函數被調用時傳入的所有參數。如果我們在箭頭函數內部訪問 arguments,它只會訪問到外層(如果存在)傳統函數的 arguments 對象。

❌ 錯誤示範 (使用箭頭函數):

停止濫用箭頭函數:這5個場景請務必使用 function_構造函數_05


✅ 正確姿勢 (使用 function):

停止濫用箭頭函數:這5個場景請務必使用 function_ES6_06


注意: 在現代 JavaScript 中,更推薦使用剩餘參數 (...args) 來處理不確定數量的參數。剩餘參數是真正的數組,並且它在箭頭函數和傳統函數中都能正常工作。但如果我們需要維護舊代碼,或者有特殊理由需要使用 arguments 對象,那麼 function 是我們唯一的選擇。

停止濫用箭頭函數:這5個場景請務必使用 function_構造函數_07


那麼,什麼時候應該用箭頭函數?

箭頭函數依然非常優秀和極為有用,它的主要優勢在於其詞法 this 綁定,完美解決了過去 var self = this 或 .bind(this) 的冗長寫法。

最佳使用場景:

  • 回調函數:尤其是在 mapfilterforEach 等數組方法中,或者在 setTimeoutPromise.then 內部,當我們需要保持外部 this 上下文時。
const timer = {
  seconds: 0,
  start() {
    setInterval(() => {
      // 這裏的 this 正確地指向 timer 對象,因為箭頭函數捕獲了 start 方法的 this
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
};

timer.start();

停止濫用箭頭函數:這5個場景請務必使用 function_構造函數_08