本文 AI 產出,尚未審核

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/exportIIFE 隱蔽作用域。
  • 在大型專案中,採用 命名空間(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 的函式世界裡玩得開心,寫出更優雅的程式!