JavaScript 函式參數與預設值
簡介
在日常的前端開發中,函式是最常用的抽象單位。無論是處理使用者輸入、呼叫 API,或是執行動畫,幾乎都會寫大量的函式。
函式的參數決定了它可以接受哪種資訊,而預設值則讓函式在呼叫者忘記提供某些參數時,仍能安全且可預測地執行。掌握參數與預設值的寫法與原理,能顯著提升程式的可讀性、可維護性與防呆能力,是每位 JavaScript 開發者必備的基礎功。
本篇文章將從語法、執行機制、常見陷阱,到實務上的最佳實踐與應用情境,完整說明「函式參數與預設值」的核心概念,讓你在寫程式時不再因參數錯位或未傳值而卡關。
核心概念
1. 參數的基本寫法
在 JavaScript 中,函式的參數列在圓括號內,呼叫時依序傳入對應的值。若呼叫時少傳參數,未提供的參數會自動得到 undefined。
function greet(name) {
console.log(`Hello, ${name}!`);
}
greet('Alice'); // Hello, Alice!
greet(); // Hello, undefined!
註:
undefined代表「未定義」的值,若直接使用會產生NaN、字串"undefined"等意外結果。
2. ES6 以前的預設值寫法
在 ES6 (ES2015) 之前,我們只能在函式內部手動檢查參數是否為 undefined,再自行賦值。
function add(a, b) {
// 若 b 為 undefined,則預設為 0
if (b === undefined) {
b = 0;
}
return a + b;
}
console.log(add(5, 3)); // 8
console.log(add(5)); // 5
這種寫法雖然可行,但冗長且易忘,尤其當參數很多時更顯笨拙。
3. ES6+ 的預設參數語法
ES6 引入了預設參數(default parameters),直接在參數列表中指定預設值,語法簡潔且易讀。
function multiply(x, y = 1) {
return x * y;
}
console.log(multiply(7, 2)); // 14
console.log(multiply(7)); // 7 (y 會自動使用預設值 1)
- 預設值可以是任意表達式,甚至是函式呼叫的結果。
- 預設值只有在傳入的參數為
undefined時才會生效,傳入null、0、空字串等仍會保留原值。
function greet(name = '匿名使用者') {
console.log(`Hi, ${name}`);
}
greet(null); // Hi, null (null 不是 undefined)
greet(); // Hi, 匿名使用者
4. 參數解構與預設值的結合
當函式接受 物件 或 陣列 作為參數時,常會使用解構賦值(destructuring)配合預設值,讓呼叫者只需提供需要的屬性。
function createUser({ name = 'Guest', age = 18, isAdmin = false } = {}) {
return { name, age, isAdmin };
}
// 只提供 name,其他使用預設值
console.log(createUser({ name: 'Bob' }));
// => { name: 'Bob', age: 18, isAdmin: false }
// 完全不提供參數,整個參數物件也使用預設值 {}
console.log(createUser());
// => { name: 'Guest', age: 18, isAdmin: false }
重點: 解構參數本身也需要一個預設值(上例的
= {}),否則在呼叫createUser()時會拋出TypeError: Cannot destructure property ... of 'undefined'.
5. 參數的「剩餘」與「預設」結合(Rest Parameters)
有時需要接收不確定數量的參數,...rest 允許把多餘的參數收集成陣列。預設值仍可放在前面的普通參數上。
function sum(base = 0, ...numbers) {
return numbers.reduce((acc, cur) => acc + cur, base);
}
console.log(sum(10, 1, 2, 3)); // 16 (10 + 1 + 2 + 3)
console.log(sum()); // 0 (base 使用預設值 0,numbers 為 [])
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 預設值寫在左側 | function f(a = 1, b) {} 會讓 b 永遠得到 undefined,因為 a 的預設值不會影響 b 的傳入位置。 |
將有預設值的參數放在最右側,或使用解構物件傳參。 |
| 預設值是可變物件 | 預設值若是陣列或物件,會在函式定義時 只建立一次,多次呼叫會共享同一個參考,導致意外的副作用。 | 使用 函式返回新物件 作為預設值:function f(opts = (() => ({}))()) {} 或在函式內部重新建立。 |
| 忘記給解構參數的預設空物件 | function foo({a = 1} = {}) {} 必須在外層加 = {},否則 foo() 會拋錯。 |
總是給解構參數一個空物件預設值。 |
| 預設值依賴於其他參數 | 預設值只能使用前面已定義的參數,不能參考後面的參數。 | 把相依的計算寫在函式體內,或調整參數順序。 |
使用 arguments 與預設值混用 |
arguments 會包含所有實際傳入的參數,不會自動補上預設值,可能造成不一致。 |
盡量使用 ...rest 取代 arguments,或手動補足預設值。 |
最佳實踐
- 把有預設值的參數放在最後,或改用物件解構,提升呼叫的彈性。
- 預設值使用不可變資料(如字串、數字、
null),若需預設陣列/物件,請在函式內部建立新實例。 - 利用解構與預設值,讓 API 更具自說明性,例如
function fetchData({ url, method = 'GET' } = {})。 - 保持參數數量一致:若函式需要多個選項,盡量統一使用單一物件參數,避免位置參數錯位。
- 配合 TypeScript/JSDoc,為預設參數加上型別註記,提升編輯器自動完成與錯誤檢查。
實際應用場景
1. API 呼叫封裝
在前端專案中,常會寫一個統一的 request 函式,讓每次呼叫 API 時只需提供必要資訊,其他參數使用預設值。
function request({
url,
method = 'GET',
headers = { 'Content-Type': 'application/json' },
body = null,
timeout = 5000
} = {}) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);
return fetch(url, {
method,
headers,
body: body ? JSON.stringify(body) : null,
signal: controller.signal
})
.finally(() => clearTimeout(id));
}
// 呼叫時只寫 url,其他自動套用預設
request({ url: '/api/users' })
.then(res => res.json())
.then(data => console.log(data));
2. 表單驗證函式
表單欄位的驗證規則常會有「必填」與「長度」等選項,使用預設值可以讓開發者只關注需要變動的規則。
function validate(value, {
required = false,
minLength = 0,
maxLength = Infinity,
pattern = null
} = {}) {
if (required && !value) return false;
if (value.length < minLength) return false;
if (value.length > maxLength) return false;
if (pattern && !pattern.test(value)) return false;
return true;
}
// 只需要驗證必填與最小長度
const ok = validate('abc', { required: true, minLength: 3 });
console.log(ok); // true
3. 動畫參數設定
在使用 requestAnimationFrame 或第三方動畫函式庫時,常會提供多個可選參數,如持續時間、緩動函式等。
function animate({
duration = 300,
easing = t => t, // linear
onUpdate = () => {}
} = {}) {
const start = performance.now();
function step(timestamp) {
const elapsed = timestamp - start;
const progress = Math.min(elapsed / duration, 1);
const eased = easing(progress);
onUpdate(eased);
if (progress < 1) requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
// 呼叫時只想改變緩動函式
animate({
easing: t => t * t,
onUpdate: v => console.log(`progress: ${v}`)
});
總結
函式參數與預設值是 JavaScript 中提升程式彈性與安全性的關鍵工具。透過 ES6+ 的預設參數語法、解構賦值、以及 Rest Parameters,我們可以寫出簡潔、易讀且防呆的函式 API。
在實務開發中,務必注意「預設值的可變物件共享問題」與「解構參數的空物件預設」等常見陷阱,並遵循 將預設參數放在最右側、使用不可變資料作預設值、統一以物件傳遞多個選項 等最佳實踐。
掌握了這些概念後,你將能更自如地設計函式介面,減少錯誤、提升程式可維護性,為大型前端專案奠定穩固的基礎。祝你寫程式愉快,持續探索 JavaScript 的無限可能!