JavaScript – 函式(Functions)
主題:箭頭函式(Arrow Functions)
簡介
在 ES6(ECMAScript 2015)之後,箭頭函式(Arrow Function)成為 JavaScript 開發者日常撰寫程式碼時最常使用的語法之一。相較於傳統的 function 宣告,箭頭函式不僅語法更簡潔,還在 this、arguments、super、new.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(如在事件處理器、setTimeout、Promise內部)時,箭頭函式是最簡潔的解法。
3. arguments、super、new.target
arguments:箭頭函式沒有自己的arguments物件,會直接存取外層函式的arguments。super、new.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:陣列的 map、filter、reduce
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
說明:使用箭頭函式的
map、filter、reduce,讓程式碼行數減少,且可直接閱讀到「資料流」的意圖。
範例 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;
});
}
說明:箭頭函式在
then、catch中保持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。 |
若需要建構子,仍使用傳統 function 或 class。 |
| 物件字面量隱式回傳失效 | => { a: 1 } 會被視為程式區塊,回傳 undefined。 |
使用圓括號包住物件:=> ({ a: 1 })。 |
意外捕捉外層 arguments |
箭頭函式沒有自己的 arguments,可能導致參數錯誤。 |
使用剩餘參數語法 (...args) 替代。 |
在方法中使用箭頭函式導致 this 永遠指向類別實例 |
若需要 動態 的 this(如作為事件委派的代理),不應使用箭頭函式。 |
只在需要固定 this 的情境使用;否則保留傳統函式或 bind。 |
| 過度濫用單行寫法降低可讀性 | 雖然單行寫法簡潔,但過長的表達式會讓人難以追蹤。 | 當表達式超過兩三個操作時,建議使用大括號與 return,或拆分為多行。 |
最佳實踐:
- 保持語意清晰:若函式僅做「資料轉換」或「回呼」且不需要
this,優先使用箭頭函式。 - 避免在物件方法中直接使用箭頭函式(除非確定不會需要動態
this)。 - 使用剩餘參數 (
...args) 取代arguments,提升可讀性與 TypeScript 支援度。 - 在大型專案中,統一程式碼風格(如 ESLint
prefer-arrow-callback規則),確保團隊成員對箭頭函式的使用有共識。 - 適度加上註解:即使語法簡潔,對於非直觀的行為(如
this捕捉)仍應加以說明。
實際應用場景
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>; };- React 推崇使用箭頭函式撰寫事件處理器與內部函式,因為
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); };- 在
函式式程式設計(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測試框架的 Mock 函式
- Jest、Mocha 等測試工具常以箭頭函式撰寫簡短的 stub 或 spy。
const mock = jest.fn(x => x * 10); expect(mock(5)).toBe(50);
總結
- 箭頭函式提供了更簡潔的語法與 詞法
this捕捉機制,讓回呼、非同步流程與函式式程式設計變得更直觀。 - 了解 何時使用、何時避免 使用箭頭函式,是寫出可讀、可維護程式碼的關鍵。
- 常見陷阱(如
new、arguments、物件回傳)只要記住規則,就能輕鬆避開。 - 在 React、Node.js、函式式編程、測試 等實務情境中,善用箭頭函式能顯著減少樣板程式碼,提高開發效率。
掌握箭頭函式,不只是學會一個語法糖,而是讓你的 JavaScript 程式碼 更具表意、更安全、更易維護。祝你在日後的開發旅程中,寫出乾淨、優雅的函式!