本文 AI 產出,尚未審核

JavaScript – 函式(Functions)

主題:箭頭函式(Arrow Functions)


簡介

在 ES6(ECMAScript 2015)之後,箭頭函式(Arrow Function)成為 JavaScript 開發者日常撰寫程式碼時最常使用的語法之一。相較於傳統的 function 宣告,箭頭函式不僅語法更簡潔,還在 thisargumentssupernew.target 等關鍵字的行為上提供了更直觀的預設值,讓開發者在撰寫回呼(callback)或函式式程式(functional programming)時,能減少大量樣板程式碼。

本文將從 語法結構this 綁定機制實用範例常見陷阱最佳實踐,一步步帶你深入了解箭頭函式的運作原理,並說明在真實專案中如何善加利用。


核心概念

1. 基本語法

傳統函式 箭頭函式
function add(a, b) { return a + b; } const add = (a, b) => a + b;
  • 參數:若只有一個參數,括號可省略 x => x * 2
  • 函式主體:若主體只有單一表達式,且直接回傳結果,大括號與 return 可省略
  • 多行程式:需要大括號與 return
// 多行範例
const filterEven = arr => {
  return arr.filter(num => num % 2 === 0);
};

2. this 的行為

傳統函式的 this 取決於 呼叫方式(如直接呼叫、作為物件方法、使用 new、或 call/apply/bind),而 箭頭函式的 this 永遠是外層(詞法)環境的 this,即在宣告時就「捕捉」住所在的 this

const obj = {
  id: 1,
  // 傳統函式:this 依呼叫方式而變
  traditional() {
    setTimeout(function () {
      console.log('traditional this.id =', this.id); // undefined
    }, 100);
  },
  // 箭頭函式:this 直接繼承自 obj
  arrow() {
    setTimeout(() => {
      console.log('arrow this.id =', this.id); // 1
    }, 100);
  }
};

obj.traditional(); // => undefined
obj.arrow();       // => 1

重點:在需要保留外層 this(如在事件處理器、setTimeoutPromise 內部)時,箭頭函式是最簡潔的解法

3. argumentssupernew.target

  • arguments:箭頭函式沒有自己的 arguments 物件,會直接存取外層函式的 arguments
  • supernew.target:同樣遵循詞法繼承原則,僅在類別(class)中有意義。
function sum() {
  // 傳統函式有自己的 arguments
  const arrowSum = (...args) => args.reduce((a, b) => a + b, 0);
  // arrowSum 直接使用外層 arguments(此處不需要)
  return arrowSum();
}

4. 不可作為建構子(Constructor)

因為箭頭函式沒有 [[Construct]] 內部方法,不能使用 new 產生實例,若嘗試會拋出 TypeError

const Person = (name) => {
  this.name = name;
};

new Person('Alice'); // TypeError: Person is not a constructor

5. 省略大括號的隱式回傳

若回傳的是 物件字面量,必須將物件包在圓括號內,避免被解析成程式區塊。

const createUser = (id, name) => ({ id, name });
// 等同於
// const createUser = function(id, name) {
//   return { id: id, name: name };
// };

程式碼範例

以下提供 5 個實務上常見的箭頭函式範例,每段程式碼皆附上說明。

範例 1:陣列的 mapfilterreduce

const numbers = [1, 2, 3, 4, 5];

// 1. map:每個元素乘以 2
const doubled = numbers.map(n => n * 2);
// => [2, 4, 6, 8, 10]

// 2. filter:保留偶數
const evens = numbers.filter(n => n % 2 === 0);
// => [2, 4]

// 3. reduce:計算總和
const sum = numbers.reduce((acc, cur) => acc + cur, 0);
// => 15

說明:使用箭頭函式的 mapfilterreduce,讓程式碼行數減少,且可直接閱讀到「資料流」的意圖。

範例 2:事件處理器中的 this 捕捉

class Timer {
  constructor() {
    this.seconds = 0;
    // 使用箭頭函式保留 this
    this.interval = setInterval(() => {
      this.seconds++;
      console.log(`已經過 ${this.seconds} 秒`);
    }, 1000);
  }

  stop() {
    clearInterval(this.interval);
  }
}

const timer = new Timer();
// 1 秒後會持續輸出「已經過 1 秒、2 秒…」
// 若改用傳統函式,this 會變成全域物件 (window)。

範例 3:Promise 鏈的錯誤傳遞

function fetchData(url) {
  return fetch(url)
    .then(res => {
      if (!res.ok) throw new Error('Network response was not ok');
      return res.json();
    })
    .then(data => {
      // 直接返回處理後的結果
      return data.map(item => ({
        id: item.id,
        title: item.title.toUpperCase()
      }));
    })
    .catch(err => {
      console.error('Fetch error:', err);
      // 重新拋出讓上層可以捕獲
      throw err;
    });
}

說明:箭頭函式在 thencatch 中保持 this 為外層(此例無 this),讓程式碼更易閱讀。

範例 4:高階函式(Higher‑Order Function)與柯里化(Currying)

// 柯里化:先傳入加法的基礎值,再回傳接受第二個值的函式
const add = a => b => a + b;

const addFive = add(5);
console.log(addFive(3)); // 8
console.log(add(2)(7));  // 9

說明:箭頭函式天然支援 返回另一個函式,寫出柯里化或偏函式(partial application)時格外簡潔。

範例 5:在類別中使用靜態方法的箭頭函式

class MathUtil {
  // 靜態屬性保存一個箭頭函式
  static sum = (...nums) => nums.reduce((a, b) => a + b, 0);
};

console.log(MathUtil.sum(1, 2, 3, 4)); // 10

說明:ES2022 允許在類別中直接宣告屬性(包括箭頭函式),可作為 工具類別 的簡潔寫法。


常見陷阱與最佳實踐

陷阱 說明 解決方式
誤以為箭頭函式可以使用 new 箭頭函式不是建構子,會拋 TypeError 若需要建構子,仍使用傳統 functionclass
物件字面量隱式回傳失效 => { a: 1 } 會被視為程式區塊,回傳 undefined 使用圓括號包住物件:=> ({ a: 1 })
意外捕捉外層 arguments 箭頭函式沒有自己的 arguments,可能導致參數錯誤。 使用剩餘參數語法 (...args) 替代。
在方法中使用箭頭函式導致 this 永遠指向類別實例 若需要 動態this(如作為事件委派的代理),不應使用箭頭函式。 只在需要固定 this 的情境使用;否則保留傳統函式或 bind
過度濫用單行寫法降低可讀性 雖然單行寫法簡潔,但過長的表達式會讓人難以追蹤。 當表達式超過兩三個操作時,建議使用大括號與 return,或拆分為多行。

最佳實踐

  1. 保持語意清晰:若函式僅做「資料轉換」或「回呼」且不需要 this,優先使用箭頭函式。
  2. 避免在物件方法中直接使用箭頭函式(除非確定不會需要動態 this)。
  3. 使用剩餘參數 (...args) 取代 arguments,提升可讀性與 TypeScript 支援度。
  4. 在大型專案中,統一程式碼風格(如 ESLint prefer-arrow-callback 規則),確保團隊成員對箭頭函式的使用有共識。
  5. 適度加上註解:即使語法簡潔,對於非直觀的行為(如 this 捕捉)仍應加以說明。

實際應用場景

  1. React 函式組件(Functional Components)

    • React 推崇使用箭頭函式撰寫事件處理器與內部函式,因為 this 永遠指向組件本身(或根本不需要 this),可以避免手動 bind
    const Counter = () => {
      const [count, setCount] = useState(0);
      const increment = () => setCount(c => c + 1); // 箭頭函式
      return <button onClick={increment}>+ {count}</button>;
    };
    
  2. Node.js 中的非同步流程

    • async/await 搭配 Array.prototype.map 時,常使用箭頭函式保持程式碼直觀。
    const urls = ['a.json', 'b.json'];
    const fetchAll = async () => {
      const results = await Promise.all(urls.map(u => fetch(u).then(r => r.json())));
      console.log(results);
    };
    
  3. 函式式程式設計(Functional Programming)

    • 高階函式、柯里化、組合子(combinators)皆以箭頭函式為基礎,使程式碼更具宣告性。
    const compose = (f, g) => x => f(g(x));
    const double = n => n * 2;
    const square = n => n ** 2;
    const doubleThenSquare = compose(square, double);
    console.log(doubleThenSquare(3)); // (3*2)^2 = 36
    
  4. 測試框架的 Mock 函式

    • Jest、Mocha 等測試工具常以箭頭函式撰寫簡短的 stub 或 spy。
    const mock = jest.fn(x => x * 10);
    expect(mock(5)).toBe(50);
    

總結

  • 箭頭函式提供了更簡潔的語法與 詞法 this 捕捉機制,讓回呼、非同步流程與函式式程式設計變得更直觀。
  • 了解 何時使用、何時避免 使用箭頭函式,是寫出可讀、可維護程式碼的關鍵。
  • 常見陷阱(如 newarguments、物件回傳)只要記住規則,就能輕鬆避開。
  • React、Node.js、函式式編程、測試 等實務情境中,善用箭頭函式能顯著減少樣板程式碼,提高開發效率。

掌握箭頭函式,不只是學會一個語法糖,而是讓你的 JavaScript 程式碼 更具表意、更安全、更易維護。祝你在日後的開發旅程中,寫出乾淨、優雅的函式!