TypeScript 函式(Functions)— 預設值參數
簡介
在日常開發中,我們常會遇到需要為函式參數提供「預設值」的情況。若呼叫端忘記傳入某個參數,函式仍能以合理的預設行為繼續執行,這不僅提升程式的容錯性,也能讓 API 更易於使用。
在 TypeScript 中,預設值參數(default parameters)不只是 JavaScript 的語法糖,更結合了型別系統的檢查,使得開發者在編寫與呼叫函式時能得到更完整的 IntelliSense 與編譯期錯誤提示。本文將從語法到實務應用,完整說明如何在 TypeScript 中善用預設值參數。
核心概念
1. 基本語法
在函式宣告的參數列表中,直接以 = 指定預設值即可。若呼叫時省略該參數,TypeScript 會自動使用預設值。
function greet(name: string = "訪客"): string {
return `哈囉,${name}!`;
}
// 呼叫方式
greet(); // => "哈囉,訪客!"
greet("小明"); // => "哈囉,小明!"
name: string = "訪客"同時描述了參數的型別與預設值。- 若傳入
undefined,也會觸發預設值的使用;傳入null則不會。
greet(undefined); // 使用預設值 => "哈囉,訪客!"
greet(null); // 直接傳入 null => "哈囉,null!"
2. 預設值與型別推斷
若預設值本身能明確推斷出型別,參數的型別可以省略,TypeScript 會自動推斷。
function add(a: number, b = 0): number {
return a + b;
}
// b 的型別被推斷為 number
add(5); // => 5
add(5, 3); // => 8
小技巧:在大型函式中,建議仍明確寫出型別,避免未來維護時產生歧義。
3. 多個參數的預設值
預設值參數可以出現在任意位置,但若在 非最後 的參數使用,呼叫端必須顯式傳入 undefined 以觸發預設值,否則後面的參數會被錯位。
function createUser(name: string, isAdmin: boolean = false, age?: number) {
return { name, isAdmin, age };
}
// 正確使用預設值
createUser("阿美"); // isAdmin = false
createUser("阿美", undefined, 30); // isAdmin = false, age = 30
// 錯誤示範:省略 isAdmin 直接傳入 age,會被當作 isAdmin
createUser("阿美", 30); // 編譯錯誤:Argument of type 'number' is not assignable to parameter of type 'boolean'.
建議:將所有有預設值的參數放在參數列表的最後,或使用 物件解構 方式傳遞參數(下節會說明)。
4. 物件解構與預設值
在許多 API 設計中,會以單一「設定物件」的方式傳遞多個參數。結合解構與預設值,可同時解決參數順序與可選性的問題。
interface FetchOptions {
url: string;
method?: "GET" | "POST";
timeout?: number;
}
function fetchData({ url, method = "GET", timeout = 5000 }: FetchOptions): Promise<string> {
// 假裝呼叫 fetch...
return Promise.resolve(`Fetched ${url} with ${method} (timeout ${timeout}ms)`);
}
// 呼叫方式
fetchData({ url: "/api/users" }); // method = GET, timeout = 5000
fetchData({ url: "/api/users", method: "POST" }); // timeout = 5000
- 解構的參數本身已是 必填(
url),其他則有預設值。 - 呼叫端只需要提供需要改變的欄位,省去繁瑣的參數順序。
5. 預設值與函式重載(overload)
有時候想要根據不同的參數組合提供不同的回傳型別,這時可以結合 函式重載 與 預設值。
// 重載簽名
function format(value: number, locale?: string): string;
function format(value: Date, locale?: string): string;
// 實作
function format(value: number | Date, locale: string = "en-US"): string {
if (typeof value === "number") {
return new Intl.NumberFormat(locale).format(value);
} else {
return new Intl.DateTimeFormat(locale).format(value);
}
}
// 使用
format(1234567); // "1,234,567"
format(new Date(), "zh-TW"); // "2025/11/19"
- 預設值
locale = "en-US"只在未提供時套用。 - 重載讓編譯器在呼叫時能正確推斷回傳型別。
程式碼範例
範例 1:簡易計算器
function calculate(
a: number,
b: number,
operator: "+" | "-" | "*" | "/" = "+"
): number {
switch (operator) {
case "+": return a + b;
case "-": return a - b;
case "*": return a * b;
case "/": return a / b;
}
}
// 預設使用加法
console.log(calculate(5, 3)); // 8
console.log(calculate(5, 3, "*")); // 15
範例 2:日誌函式(支援可選的等級)
type LogLevel = "debug" | "info" | "warn" | "error";
function log(message: string, level: LogLevel = "info"): void {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`);
}
log("系統啟動"); // 預設 info
log("資料庫連線失敗", "error");
範例 3:API 請求封裝(使用解構)
interface RequestConfig {
url: string;
method?: "GET" | "POST" | "PUT" | "DELETE";
headers?: Record<string, string>;
timeout?: number;
}
function request({
url,
method = "GET",
headers = { "Content-Type": "application/json" },
timeout = 3000,
}: RequestConfig): Promise<Response> {
return fetch(url, { method, headers })
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res;
})
.catch(err => {
console.error(`Request failed: ${err.message}`);
throw err;
});
}
// 呼叫
request({ url: "/api/items" }); // GET, 預設 header & timeout
request({ url: "/api/items", method: "POST", timeout: 5000 });
範例 4:函式重載 + 預設值(格式化)
function format(value: number, locale?: string): string;
function format(value: Date, locale?: string): string;
function format(value: number | Date, locale: string = "en-US"): string {
return typeof value === "number"
? new Intl.NumberFormat(locale).format(value)
: new Intl.DateTimeFormat(locale).format(value);
}
// 使用
console.log(format(123456)); // "123,456"
console.log(format(new Date(), "ja-JP"));
範例 5:可變參數與預設值的結合
function concat(separator: string = ", ", ...items: string[]): string {
return items.join(separator);
}
console.log(concat(" - ", "A", "B", "C")); // "A - B - C"
console.log(concat(undefined, "X", "Y")); // 使用預設分隔符 => "X, Y"
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 預設值放在非最後參數 | 呼叫時若省略中間參數,後面的參數會被錯位。 | 將所有預設值參數搬到最後,或改用 物件解構。 |
使用 null 代替 undefined |
null 會直接傳給函式,不會觸發預設值。 |
若想使用預設值,請傳 undefined 或省略參數。 |
| 預設值是物件或陣列時共享引用 | 預設值在函式定義時只建立一次,會導致多次呼叫共享同一個物件。 | 使用 函式返回的工廠 或在函式內部 = [] → = (() => [])() 以產生新實例。 |
| 預設值與泛型結合時失去型別推斷 | 預設值可能會讓 TypeScript 無法正確推斷泛型。 | 明確指定泛型或使用 條件型別 來保留推斷。 |
| 過度依賴預設值導致 API 不易閱讀 | 太多預設值會讓呼叫端不清楚實際行為。 | 在文件或 JSDoc 中說明每個預設值的意圖,並盡量保持 簡潔。 |
最佳實踐
- 預設值放最後:保持函式簽名的可預測性。
- 使用解構物件:解決多參數、可選參數的順序問題。
- 避免可變物件作為預設值:使用工廠函式或
Object.assign({}, defaultObj)。 - 為預設值加上 JSDoc:提升 IDE 裡的說明與自動完成。
- 結合型別別名或介面:讓預設值的型別更清晰,減少維護成本。
實際應用場景
設定檔與套件初始化
大多數第三方套件會接受一個「設定物件」作為參數,透過預設值讓使用者只必須提供需要自訂的部分。initLibrary({ apiKey: "xxx" }); // 只提供必要欄位,其餘使用預設表單驗證函式
驗證規則常有「是否必填」等可選屬性,預設值可以讓驗證器在未傳入時自動使用慣用規則。API 請求的重試機制
maxRetries、backoff等參數可設為預設值,讓呼叫端在大多數情況下不必自行設定。日誌與監控
預設的日誌等級、輸出格式、目的地(console、file)均可透過預設值簡化初始化流程。多語系或在地化
函式接受locale參數時,將locale = "en-US"作為預設,可讓程式在未提供語系時仍能正常運作。
總結
預設值參數是 TypeScript 中提升函式彈性與可讀性的關鍵特性。透過簡潔的語法,我們可以:
- 為可選參數提供安全的預設行為,減少
undefined錯誤。 - 結合 物件解構,解決參數順序與可選性的痛點。
- 在 函式重載、泛型 以及 可變參數 中靈活運用。
在實務開發中,遵守「預設值放最後」的原則、避免共享可變物件、並以 JSDoc 或介面說明每個預設值的意圖,能讓程式碼更易於維護與擴充。希望本篇文章能幫助你在日常開發裡,寫出更安全、直觀且具備彈性的 TypeScript 函式。祝你寫程式快樂!