本文 AI 產出,尚未審核

JavaScript 函式宣告與表達式

簡介

在 JavaScript 中,函式(function) 是最核心的抽象概念之一。它不僅是執行程式碼的基本單位,更是實作模組化、重用性與非同步流程的關鍵工具。無論是簡單的表單驗證、複雜的 UI 組件,甚至是伺服器端的 API 處理,都離不開函式的使用。

本單元聚焦在「函式宣告」與「函式表達式」兩種寫法的差異與適用時機。了解它們的行為、提升程式可讀性與除錯效率,是從 初學者 成長為 中級開發者 必備的基礎功。


核心概念

1. 函式宣告(Function Declaration)

function greet(name) {
  console.log(`哈囉,${name}!`);
}
  • 語法:以 function 關鍵字開頭,接著函式名稱、參數列表、函式本體。
  • 提升(Hoisting):宣告會在程式執行前被提升(hoisted)到所在作用域的最上方,意味著可以在宣告之前呼叫。
// 先呼叫,後宣告 —— 不會拋出 ReferenceError
sayHello('小明');

function sayHello(person) {
  console.log(`你好,${person}`);
}

2. 函式表達式(Function Expression)

const add = function (a, b) {
  return a + b;
};
  • 語法:將函式(匿名或具名)賦值給變數、屬性或陣列元素。
  • 不會提升:變數本身會提升(var)或在塊級作用域(let / const)保持暫時性死區(TDZ),但函式本身不會提前可用。
// 呼叫會拋出錯誤:add is not defined
try {
  console.log(add(2, 3));
} catch (e) {
  console.error(e);
}

// 正確寫法:先宣告再呼叫
const add = function (a, b) {
  return a + b;
};
console.log(add(2, 3)); // 5

3. 命名函式表達式(Named Function Expression)

const factorial = function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1); // 使用內部名稱遞迴
};
  • 內部名稱 fact 只在函式本體內部可見,對外部保持匿名,有助於除錯(堆疊追蹤)與遞迴。

4. 箭頭函式(Arrow Function)—— 表達式的一種

const multiply = (x, y) => x * y;
  • 沒有自己的 thisarguments,適合簡潔的回呼(callback)或純函式運算。

程式碼範例

範例 1:使用提升的函式宣告建立模組化工具

// utils.js
function isPrime(num) {
  if (num <= 1) return false;
  for (let i = 2; i <= Math.sqrt(num); i++) {
    if (num % i === 0) return false;
  }
  return true;
}

// 直接呼叫,即使在檔案底部
console.log(isPrime(17)); // true

重點:函式宣告在同一作用域內可自由前後排列,讓檔案結構更彈性。

範例 2:利用函式表達式建立閉包(Closure)

function createCounter() {
  let count = 0; // 私有變數
  return function () { // 匿名函式表達式
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

說明:因為表達式返回的是 函式物件,每次呼叫都能存取同一個 count,形成閉包。

範例 3:命名函式表達式的遞迴

const fibonacci = function fib(n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2);
};

console.log(fibonacci(6)); // 8

優點:即使把 fibonacci 重新指派給其他變數,遞迴仍會正確呼叫內部名稱 fib

範例 4:箭頭函式作為陣列方法的回呼

const numbers = [1, 2, 3, 4, 5];
const squares = numbers.map(n => n * n);

console.log(squares); // [1, 4, 9, 16, 25]

提醒:箭頭函式不會自行綁定 this,在需要存取呼叫者物件時仍須使用普通函式。

範例 5:混用提升與表達式,避免「未定義」錯誤

// 正確:先宣告再使用
const greet = function (msg) {
  console.log(`訊息:${msg}`);
};

greet('Hello'); // 訊息:Hello

// 錯誤示範:提升的誤用
try {
  shout('Oops');
} catch (e) {
  console.error(e); // ReferenceError: Cannot access 'shout' before initialization
}

let shout = (text) => console.log(text.toUpperCase());

常見陷阱與最佳實踐

陷阱 說明 解決方式
提升誤用 把函式表達式當成宣告使用,導致 ReferenceError 使用 const / let 明確宣告,或改用函式宣告。
命名衝突 在同一作用域內同名的函式宣告會被後者覆蓋。 盡量使用 模組化export / import)或 IIFE 隔離作用域。
this 失效 在回呼中使用普通函式會改變 this,導致錯誤。 使用箭頭函式或 bind 明確綁定 this
過度使用匿名函式 堆疊追蹤時看不到函式名稱,除錯困難。 為重要函式使用 命名函式表達式
TDZ(暫時性死區) let / const 變數在宣告前不可存取。 保持宣告在使用之前,或改用 var(但要注意提升行為)。

最佳實踐

  1. 一致性:在同一檔案或專案中,統一使用函式宣告或表達式的風格,便於團隊閱讀。
  2. 模組化:配合 ES6 export / import,將函式封裝成獨立模組,降低全域汙染。
  3. 命名:對外暴露的函式使用具意義的名稱,對內部回呼可使用匿名或命名表達式,視除錯需求而定。
  4. 避免副作用:函式內部盡量不直接修改外部變數,除非設計為 純函式(pure function)。
  5. 文件化:使用 JSDoc 為函式加上參數、回傳值說明,提升自動產生文件的品質。

實際應用場景

1. 前端表單驗證

// 使用函式宣告,讓驗證規則可在檔案任意位置呼叫
function isEmail(str) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(str);
}

2. 事件處理器(Event Handler)

// 使用箭頭函式避免 this 被改寫
document.getElementById('btn').addEventListener('click', (e) => {
  e.target.disabled = true; // this === 按鈕
  // 執行非同步操作...
});

3. API 客戶端封裝

// 函式表達式 + async/await,形成可重用的 HTTP 呼叫
const apiGet = async function (url) {
  const response = await fetch(url);
  if (!response.ok) throw new Error('Network error');
  return response.json();
};

export { apiGet };

4. 伺服器端(Node.js)路由定義

// 使用命名函式表達式,讓錯誤堆疊顯示具體名稱
app.get('/users/:id', function getUser(req, res, next) {
  const id = req.params.id;
  // 取得使用者資料...
});

5. 高階函式(Higher‑Order Function)

// 以函式表達式回傳另一個函式,實作防抖(debounce)
function debounce(delay) {
  let timer;
  return function (fn) {
    clearTimeout(timer);
    timer = setTimeout(fn, delay);
  };
}

總結

  • 函式宣告 具備提升特性,適合在同一作用域內自由排列;
  • 函式表達式(包括箭頭函式)不會提升,能與變數的作用域結合,形成閉包或高階函式;
  • 命名函式表達式 為除錯與遞迴提供便利;
  • 正確掌握 提升、TDZ、this 綁定 等概念,可避免常見錯誤,寫出更可維護的程式碼。

透過本單元的學習,你應該已能在實務開發中自如選擇適當的函式寫法,提升程式的可讀性、可測試性與效能。持續在專案中練習、閱讀開源碼,才能將這些概念內化為日常開發的第二天性。祝你寫程式愉快!