JavaScript ES6+ 新特性:箭頭函式 (Arrow Function)
簡介
在 ES6 以前,我們撰寫函式大多使用 function 關鍵字。雖然功能完整,但在語法上往往較為冗長,且 this 綁定 的行為容易讓新手感到困惑。
ES6 引入的 箭頭函式(Arrow Function)以更簡潔的語法取代傳統函式,讓程式碼更易讀、寫起來更流暢,同時也改變了 this、arguments、super 與 new.target 的繫結方式,成為現代 JavaScript(Modern JS)開發的常用工具。
掌握箭頭函式不僅能提升開發效率,還能避免許多因 this 指向錯誤而產生的 bug,對於從前端框架(如 React、Vue)到 Node.js 後端服務,都是必備的基礎知識。
核心概念
1. 基本語法
傳統函式:
function add(a, b) {
return a + b;
}
等價的箭頭函式:
const add = (a, b) => a + b;
- 參數:若只有一個參數,圓括號可省略;若沒有參數,必須寫
() =>。 - 函式主體:若只有單一表達式且直接回傳,省略大括號與
return;若需要多行程式碼或執行副作用,則需加上大括號與return(若有回傳值)。
// 無參數
const getTime = () => Date.now();
// 單一參數
const double = n => n * 2;
// 多行程式碼
const filterEven = arr => {
const result = [];
for (const n of arr) {
if (n % 2 === 0) result.push(n);
}
return result;
};
2. this 的行為
傳統函式 在被呼叫時會自行決定 this 的指向(依照呼叫方式或 call/apply/bind)。
箭頭函式 則不會產生自己的 this,它會 「捕獲」(lexical capture)外層執行環境的 this,相當於把外層的 this「閉包」進來。
const obj = {
value: 42,
// 傳統函式:this 依照呼叫方式決定
getValue() {
setTimeout(function () {
console.log('傳統函式 this.value =', this.value);
}, 100);
},
// 箭頭函式:this 直接繼承自 obj
getValueArrow() {
setTimeout(() => {
console.log('箭頭函式 this.value =', this.value);
}, 100);
}
};
obj.getValue(); // undefined(因為 this 指向全域或 undefined)
obj.getValueArrow(); // 42
重點:在需要保留外層
this(例如在回呼、setTimeout、Promise中)時,箭頭函式是最直觀的解決方案。
3. arguments、super、new.target
arguments:箭頭函式不擁有自己的arguments物件,若需要存取傳入參數,可直接使用命名參數或展開運算子(...args)。super:在類別的子類別建構子或方法中,箭頭函式會繼承外層的super參照。new.target:同樣不會被箭頭函式擁有,若需要檢查是否使用new呼叫,仍須使用傳統函式或類別語法。
function traditional(...args) {
console.log(arguments); // 有 arguments 物件
}
const arrow = (...args) => {
// console.log(arguments); // ReferenceError
console.log(args); // 使用展開運算子取得參數
};
traditional(1, 2, 3);
arrow(1, 2, 3);
4. 何時不能使用箭頭函式
| 情境 | 為什麼不能使用 | 替代方案 |
|---|---|---|
需要 new 建立實例 |
箭頭函式沒有 [[Construct]] 內部方法 |
使用傳統函式或 class |
需要 this 隨呼叫者改變(如事件處理器需要 element) |
this 被 lexical 綁定 |
使用傳統函式或 event.currentTarget |
必須使用 arguments 物件 |
無 arguments |
使用 ...args 或傳統函式 |
程式碼範例
範例 1:陣列操作的簡潔寫法
const numbers = [1, 2, 3, 4, 5];
// map + arrow
const squares = numbers.map(n => n ** 2);
console.log(squares); // [1, 4, 9, 16, 25]
// filter + arrow (保留偶數)
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4]
說明:使用箭頭函式讓
map、filter等高階函式的回呼變得一目了然,避免冗長的function關鍵字。
範例 2:在 Promise 中保留 this
class ApiService {
constructor(baseURL) {
this.baseURL = baseURL;
}
fetchUser(id) {
// this 必須指向 ApiService 實例
return fetch(`${this.baseURL}/users/${id}`)
.then(res => res.json())
.then(user => {
// 使用箭頭函式,this 仍指向 ApiService
console.log('取得使用者', this.baseURL, user);
return user;
});
}
}
const api = new ApiService('https://jsonplaceholder.typicode.com');
api.fetchUser(1);
關鍵:
then內的箭頭函式自動捕獲ApiService的this,不必額外bind(this)。
範例 3:事件處理器的 this 差異
<button id="btn1">傳統函式</button>
<button id="btn2">箭頭函式</button>
<script>
document.getElementById('btn1').addEventListener('click', function () {
// this 代表被點擊的 button
console.log('傳統函式 this.id =', this.id);
});
document.getElementById('btn2').addEventListener('click', () => {
// this 來自外層 (window),不是 button
console.log('箭頭函式 this.id =', this.id);
});
</script>
實務提醒:在 DOM 事件處理時,若需要存取觸發元素,不要使用箭頭函式,除非你改用
event.currentTarget。
範例 4:使用 ...args 取代 arguments
function sumTraditional() {
// 使用 arguments
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
const sumArrow = (...args) => args.reduce((a, b) => a + b, 0);
console.log(sumTraditional(1, 2, 3, 4)); // 10
console.log(sumArrow(1, 2, 3, 4)); // 10
說明:展開運算子讓參數取得更直觀,也避免了
arguments造成的可讀性問題。
範例 5:在類別的 getter 中使用箭頭函式
class Counter {
#count = 0;
// getter 使用箭頭函式,保持 this 正確
get value() {
return (() => this.#count)();
}
increment() {
this.#count++;
}
}
const c = new Counter();
c.increment();
c.increment();
console.log(c.value); // 2
技巧:即使在 getter、setter 等特殊屬性裡,也可以利用箭頭函式確保
this的正確指向。
常見陷阱與最佳實踐
| 陷阱 | 可能的錯誤 | 解決方式 |
|---|---|---|
| 誤用於事件處理 | this 不指向目標元素,導致 undefined 或錯誤 |
使用傳統函式,或改用 event.currentTarget |
| 忘記括號 | 單一參數時省略括號,若參數是解構或預設值就會語法錯誤 | 參數有解構、預設值或型別註記時,必須加括號 |
| 返回物件時忘記括號 | => { a: 1 } 被解析為程式碼區塊,而非物件字面值 |
使用圓括號包住物件:=> ({ a: 1 }) |
使用 new |
會拋出 TypeError: ... is not a constructor |
改用傳統函式或 class |
| 過度濫用 | 把所有函式都寫成箭頭,失去可讀性或語意 | 僅在需要 lexical this、簡短回呼時使用;較複雜的邏輯仍建議使用傳統函式或 class 方法 |
最佳實踐
- 保持簡潔:單行回傳時使用隱式返回,讓意圖一目了然。
- 明確
this:在需要this隨呼叫者改變的情境,堅持使用傳統函式。 - 使用
...args:取代arguments,提升可讀性與型別推斷。 - 避免返回物件時遺漏圓括號:
const fn = () => ({ foo: 'bar' }); - 遵循團隊 lint 規範:如
eslint的prefer-arrow-callback、arrow-body-style等規則,確保程式碼風格一致。
實際應用場景
React 函式組件
- 事件處理、
useEffect回呼、map渲染列表等,都大量使用箭頭函式,讓this不必再手動綁定。
- 事件處理、
Node.js 中的非同步流程
- 在
Promise、async/await的.then、.catch、.finally中使用箭頭函式,可確保上下文一致,減少bind的使用。
- 在
高階函式庫(如 Lodash、Ramda)
- 這類函式庫鼓勵以「函式式編程」的方式撰寫程式碼,箭頭函式的簡潔語法與不可變性概念相輔相成。
Vue 3 Composition API
setup函式內部的回呼(例如watch、computed)常以箭頭函式寫法呈現,讓組件狀態更易追蹤。
測試框架(Jest、Mocha)
- 在測試案例的
it、test回呼中使用箭頭函式,可避免this被測試框架意外改寫,減少測試錯誤。
- 在測試案例的
總結
箭頭函式是 ES6+ 中最具代表性的語法糖之一,以更簡潔的寫法提供了 lexical this、arguments 的替代方案,讓開發者在處理回呼、非同步流程、陣列操作時能寫出更乾淨、可讀的程式碼。
- 何時使用:簡短回呼、需要保留外層
this、不需要new、不需要arguments。 - 何時避免:需要動態
this、必須使用new、需要arguments或new.target。
掌握上述概念與最佳實踐,您就能在 前端框架、Node.js 伺服器、函式式程式設計 等各種情境下,安全且高效地運用箭頭函式,提升程式碼品質與開發效率。
祝您在 JavaScript 的旅程中寫出 更簡潔、更可靠 的程式!