JavaScript 函式(Functions)
具名 / 匿名函式
簡介
在 JavaScript 中,函式是最核心的抽象概念之一。它不僅是執行程式碼的單位,更是建立 閉包(closure)、實作 高階函式、以及撰寫 模組化、可重用程式碼 的基石。
函式可以依照是否有名稱分為 具名函式(named function) 與 匿名函式(anonymous function)。看似微小的差別,卻會直接影響除錯、作用域、以及函式的使用方式。了解兩者的特性與適用情境,對於從「寫出可執行的程式」提升到「寫出可維護、可擴充的程式」至關重要。
本篇文章將以 繁體中文(台灣) 為語言,從概念說明、實作範例、常見陷阱、最佳實踐以及實務應用,完整闡述具名與匿名函式的差異與使用時機,幫助讀者在日常開發中做出更好的選擇。
核心概念
1. 什麼是具名函式?
具名函式(named function)是指在宣告時就已經給予函式一個 名稱,這個名稱會在 函式作用域(function scope) 內部以及外部可直接存取。常見的寫法有:
function add(a, b) {
return a + b;
}
add為函式名稱。- 函式會被提升(hoisting),在程式碼的任何位置(只要在同一作用域內)都可以呼叫。
具名函式的特點
| 特點 | 說明 |
|---|---|
| 提升(Hoisting) | 函式宣告會在執行階段前被提升,允許在宣告之前呼叫。 |
| 可自我遞迴 | 函式名稱在函式內部仍可被參考,適合遞迴實作。 |
| 除錯友好 | 錯誤堆疊(stack trace)會顯示函式名稱,便於定位問題。 |
| 全域/區域可見 | 依照宣告位置,名稱會成為該作用域的變數。 |
2. 什麼是匿名函式?
匿名函式(anonymous function)則是 沒有名稱 的函式,通常以 函式表達式(function expression) 或 箭頭函式(arrow function) 的形式出現。最常見的寫法有:
const multiply = function (a, b) {
return a * b;
};
或使用 ES6+ 的箭頭語法:
const multiply = (a, b) => a * b;
在這兩個例子中,multiply 其實是 變數,而不是函式本身的名稱。函式本身是匿名的。
匿名函式的特點
| 特點 | 說明 |
|---|---|
| 不會提升 | 只有在變數被賦值後才能使用,若在宣告前呼叫會得到 ReferenceError。 |
| 可作為參數或回傳值 | 常用於 回呼函式(callback)、即時函式(IIFE)、或 高階函式。 |
| 語意更簡潔 | 尤其在只需要一次使用的情況下,省去命名的負擔。 |
| 在遞迴時需額外處理 | 若要遞迴,需要透過變數名稱或 arguments.callee(已不建議使用)。 |
3. 具名函式 vs 匿名函式:何時該選哪一個?
| 場景 | 建議使用 | 原因 |
|---|---|---|
| 模組或公共 API | 具名函式 | 讓使用者與文件都有明確的函式名稱,提升可讀性與除錯效率。 |
一次性回呼(例如 Array.prototype.map) |
匿名函式(或箭頭函式) | 不需要在別處再次呼叫,簡潔的寫法更易閱讀。 |
| 遞迴演算法 | 具名函式或具名函式表達式 | 直接使用函式名稱遞迴,避免 arguments.callee 的非標準使用。 |
| 需要在同一作用域內多次呼叫 | 具名函式或具名函式表達式 | 變數名稱可以重複使用,避免重複宣告。 |
| 需要在閉包中保持參考 | 匿名函式(搭配變數) | 變數本身形成閉包,名稱不會污染全域。 |
程式碼範例
以下提供 5 個實用範例,展示具名與匿名函式在不同情境下的寫法與差異。每段程式碼皆附上說明註解。
範例 1:具名函式的提升(Hoisting)
// 在函式宣告之前呼叫,它仍然可以正確執行
console.log(square(5)); // => 25
function square(n) {
// 具名函式會被提升,整段函式會先被解析
return n * n;
}
重點:具名函式在執行階段前已被提升,故可以在宣告前使用。這在撰寫 測試腳本 或 分段式程式碼 時非常方便。
範例 2:匿名函式作為回呼(Callback)
const numbers = [1, 2, 3, 4, 5];
// 使用匿名函式作為 map 的回呼
const doubled = numbers.map(function (n) {
return n * 2;
});
console.log(doubled); // => [2, 4, 6, 8, 10]
說明:此函式只在
map內部使用一次,命名會增加不必要的雜訊。若改用箭頭函式,寫法更簡潔:
const doubled = numbers.map(n => n * 2);
範例 3:具名函式表達式(Named Function Expression)
有時候我們想要 保留函式名稱 以利除錯,但又想以變數形式使用,這時可以採用 具名函式表達式:
const factorial = function fact(n) {
// 這裡使用函式名稱 fact 來遞迴,而不是外部變數 factorial
if (n <= 1) return 1;
return n * fact(n - 1);
};
console.log(factorial(5)); // => 120
優點:即使外部變數
factorial被重新指派,遞迴仍會正確呼叫fact。同時,錯誤堆疊會顯示fact,提升除錯效率。
範例 4:立即執行函式(IIFE)— 常見的匿名函式
// 建立一個私有作用域,避免全域汙染
(function () {
const secret = '🧩';
console.log('IIFE 內部的 secret:', secret);
})();
// console.log(secret); // ReferenceError: secret is not defined
說明:IIFE 典型使用匿名函式,因為它只會執行一次且不需要在外部存取。這在 模組化(pre‑ES6)或 一次性初始化 時非常有用。
範例 5:使用箭頭函式的匿名函式 – 事件監聽
const button = document.querySelector('#myBtn');
// 使用箭頭函式作為事件處理器
button.addEventListener('click', (event) => {
// this 在箭頭函式裡不會被重新綁定,指向外層的 this(通常是 window)
console.log('Button 被點擊!', event.target);
});
注意:若需要存取 DOM 元素本身(即
this指向 button),應使用普通函式:
button.addEventListener('click', function () {
console.log('Button 被點擊!', this); // this === button
});
常見陷阱與最佳實踐
1. 提升(Hoisting)誤解
具名函式 會被提升,但 函式表達式(即使是匿名的)不會。以下是常見錯誤:
// 錯誤寫法:在宣告前呼叫
foo(); // TypeError: foo is not a function
var foo = function () {
console.log('Hello');
};
解決方式:將函式宣告提升或改用 let/const,確保在使用前已完成賦值。
2. 遞迴時使用匿名函式
const countDown = function (n) {
if (n <= 0) return;
console.log(n);
// 錯誤:此時 this 會指向全域,無法遞迴
// countDown(n - 1); // 若變數被重新指派會出錯
};
最佳做法:使用 具名函式表達式,或直接使用函式名稱本身:
const countDown = function cd(n) {
if (n <= 0) return;
console.log(n);
cd(n - 1); // 使用內部名稱遞迴
};
3. this 的綁定差異
- 匿名函式(普通函式)在被呼叫時會根據呼叫方式動態綁定
this。 - 箭頭函式則會 捕獲外層
this,不會自行建立新的this。
實務建議:在需要 物件方法 中使用
this時,避免使用箭頭函式;在 回呼、純計算 或 不需要this的情境下,優先考慮箭頭函式,以減少程式碼雜訊。
4. 命名衝突與全域汙染
使用具名函式時,名稱會成為所在作用域的變數。若在同一作用域內不小心重複宣告,會導致 覆寫 或 意外行為。
最佳實踐:
- 使用 模組化(ES6
import/export) 或 IIFE 隱蔽作用域。 - 在大型專案中,採用 命名空間(namespace) 或 類別(class) 包裝函式。
5. 記憶體與效能
匿名函式若在迴圈內每次產生新實例,可能會增加 記憶體占用。例如:
for (let i = 0; i < 1000; i++) {
arr.map(function (x) { return x + i; }); // 每次迭代都建立新函式
}
優化方式:將函式抽離為具名函式,或使用 閉包 只保留必要的變數。
實際應用場景
| 場景 | 具名函式的使用方式 | 匿名函式的使用方式 |
|---|---|---|
| API 客戶端 | function fetchUser(id) { … } 讓呼叫者容易閱讀與重用 |
axios.get('/users', response => { … }) 只在此處使用 |
| 事件委派 | function handleClick(event) { … }(可在多個元素間共享) |
element.addEventListener('click', e => console.log(e.target)) |
資料處理管線(如 Array.prototype.reduce) |
function sum(acc, cur) { return acc + cur; }(若在多處使用) |
arr.reduce((a, b) => a + b, 0) |
| 單元測試(Jest) | function add(a, b) { return a + b; }(測試檔案外部可直接引用) |
test('adds numbers', () => { expect(add(1,2)).toBe(3); }) |
| React 組件 | function Header(props) { … }(具名組件有助於除錯) |
props => <button onClick={() => doSomething()}>Click</button>(內嵌匿名箭頭) |
總結
- 具名函式 提供 提升、易除錯、遞迴友好 的特性,適合公開 API、模組、以及需要多次呼叫的情境。
- 匿名函式(函式表達式與箭頭函式)則在 回呼、一次性執行、閉包 等場景中更為簡潔、彈性。
- 正確掌握 提升、
this綁定、遞迴寫法,以及 作用域管理,可以避免常見陷阱,寫出更安全、可維護的程式碼。
在日常開發時,先思考函式的生命週期與使用頻率,再決定是使用具名函式還是匿名函式。這樣不僅能提升程式碼的可讀性,還能在除錯與效能上獲得實質好處。祝你在 JavaScript 的函式世界裡玩得開心,寫出更優雅的程式!