本文 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;
- 沒有自己的
this、arguments,適合簡潔的回呼(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(但要注意提升行為)。 |
最佳實踐
- 一致性:在同一檔案或專案中,統一使用函式宣告或表達式的風格,便於團隊閱讀。
- 模組化:配合 ES6
export / import,將函式封裝成獨立模組,降低全域汙染。 - 命名:對外暴露的函式使用具意義的名稱,對內部回呼可使用匿名或命名表達式,視除錯需求而定。
- 避免副作用:函式內部盡量不直接修改外部變數,除非設計為 純函式(pure function)。
- 文件化:使用 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綁定 等概念,可避免常見錯誤,寫出更可維護的程式碼。
透過本單元的學習,你應該已能在實務開發中自如選擇適當的函式寫法,提升程式的可讀性、可測試性與效能。持續在專案中練習、閱讀開源碼,才能將這些概念內化為日常開發的第二天性。祝你寫程式愉快!