JavaScript 課程 – 運算子(Operators)
主題:Null 合併運算子(??)
簡介
在日常的 JavaScript 開發中,我們常會碰到「值可能是 null 或 undefined」的情況。傳統上,開發者會使用三元運算子、||(邏輯或)或大量的 if 判斷來提供預設值,寫起來既冗長又容易出錯。ECMAScript 2020(ES11)正式加入的 Null 合併運算子 ??,正是為了解決這類需求而設計的。
?? 只在左側運算元**真的為 null 或 undefined**時才會回傳右側的預設值,與 || 不同,|| 會把所有「假值」(0、''、false、NaN…) 都視為需要替換的情況。這讓程式碼更具可讀性,也降低了因為錯誤的「假值」判斷而導致的 bug。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,甚至延伸到真實專案中的應用場景,帶你完整掌握 Null 合併運算子的用法,讓你的 JavaScript 程式碼既簡潔又安全。
核心概念
1. Null 合併運算子的語法
let result = leftOperand ?? rightOperand;
- 當
leftOperand為null或undefined時,result會取得rightOperand的值。 - 其他情況(包括
0、''、false、NaN),result直接等於leftOperand。
重點:
??僅關注 nullish(nullish = null + undefined)而非所有「假值」。
2. 為什麼 ?? 不會取代 ||?
| 運算子 | 判斷條件 | 會取代左側的情況 |
|---|---|---|
| ` | ` | |
?? |
nullish (null、undefined) |
只在左側是 null 或 undefined 時回傳右側 |
範例比較
let a = 0;
let b = a || 10; // b = 10,因為 0 被視為假值
let c = a ?? 10; // c = 0,因為 a 不是 null 或 undefined
3. 結合解構賦值與預設值
在物件或陣列解構時,我們常需要提供預設值。?? 能與解構賦值完美配合:
const { name, age = 18 } = user ?? {};
// 若 user 為 null/undefined,整個解構會使用空物件 {}
4. Null 合併賦值運算子 ??=
ES2021 引入的 Null 合併賦值運算子,可直接在變數上套用預設值:
let config = { timeout: 0 };
config.timeout ??= 3000; // 不會改變,因為 timeout 為 0(非 nullish)
config.retry ??= 3; // retry 為 undefined,會被設定為 3
程式碼範例
以下示範 5 個常見且實用的情境,幫助你快速上手 ??。
範例 1:函式參數的預設值
function greet(name) {
// 若 name 為 null/undefined,使用預設的 "Guest"
const displayName = name ?? "Guest";
console.log(`Hello, ${displayName}!`);
}
greet("Alice"); // Hello, Alice!
greet(null); // Hello, Guest!
greet(undefined); // Hello, Guest!
greet(0); // Hello, 0! // 0 不會被視為需要替換的值
說明:使用
??能正確保留0、空字串''等合法值,而不會被誤判為「未提供」。
範例 2:從 API 取得設定,提供安全的 fallback
async function loadSettings() {
const response = await fetch("/api/settings");
const data = await response.json();
// 假設 API 可能遺漏某些欄位,我們提供預設值
const theme = data.theme ?? "light";
const pageSize = data.pageSize ?? 20;
console.log(`Theme: ${theme}, PageSize: ${pageSize}`);
}
說明:即使 API 回傳
{ "theme": null },theme仍會使用"light"作為預設。
範例 3:與解構賦值結合
function init(options) {
// 若 options 為 null/undefined,使用空物件作為預設
const { host = "localhost", port = 3000 } = options ?? {};
console.log(`Server listening on ${host}:${port}`);
}
init({ host: "example.com" }); // Server listening on example.com:3000
init(null); // Server listening on localhost:3000
說明:
options ?? {}確保解構不會因null或undefined而拋出錯誤。
範例 4:Null 合併賦值運算子 ??=
let user = { name: "Bob", role: null };
// 只在 role 為 nullish 時才設定預設值
user.role ??= "guest";
console.log(user.role); // "guest"
說明:
??=能讓程式碼更簡潔,省去if (user.role == null) user.role = "guest";的寫法。
範例 5:在 React (或其他 UI 框架) 中安全渲染
function Profile({ profile }) {
// 若 profile.avatar 為 null/undefined,使用預設圖片
const avatarUrl = profile?.avatar ?? "/images/default-avatar.png";
return (
<div>
<img src={avatarUrl} alt="Avatar" />
<h2>{profile?.name ?? "匿名使用者"}</h2>
</div>
);
}
說明:結合可選鏈 (
?.) 與??,可以在一次表達式內完成「防止 null」與「提供預設」兩件事。
常見陷阱與最佳實踐
| 陷阱 | 可能的問題 | 解決方案 |
|---|---|---|
| **與 ` | ` 混用** | |
在條件運算子中使用 ?? |
a ?? b ? c : d 會因運算子優先順序產生意外結果 |
使用括號明確優先順序,例如 (a ?? b) ? c : d。 |
與可選鏈 (?.) 搭配時的順序 |
obj?.prop ?? default 與 obj?.prop ?? default 的差異不明 |
了解 ?. 先於 ??,先決定是否要存取屬性,再決定是否需要預設值。 |
| 在解構賦值時忘記提供空物件 | const { x } = null; 會拋出錯誤 |
使用 options ?? {} 或 `options |
| 在 TypeScript 中的型別推斷 | `let v: string | undefined = undefined; v = v ?? "fallback";仍保留undefined` 型別 |
最佳實踐
- 明確使用
??取代||:只在需要保留0、''、false時才選擇??。 - 搭配可選鏈 (
?.):先確保屬性存在,再使用??供預設值,寫出「安全且簡潔」的單行程式。 - 使用
??=為變數設定預設:尤其在物件初始化或設定合併時,可大幅減少冗長的if判斷。 - 保持一致的風格:團隊若決定採用
??,請在程式碼規範(如 ESLint)中加入prefer-nullish-coalescing規則,避免混用||產生不一致的行為。 - 測試「nullish」情況:寫單元測試時,特別加入
null、undefined以及0、''、false的測試案例,確保運算子行為符合預期。
實際應用場景
1. 設定檔合併
大型前端專案常有「全域設定」與「使用者個別設定」的合併需求。使用 ?? 可簡潔完成:
const globalConfig = { theme: "dark", language: "en" };
const userConfig = fetchUserConfig(); // 可能回傳 { theme: null, language: "zh-TW" }
const finalConfig = {
theme: userConfig?.theme ?? globalConfig.theme,
language: userConfig?.language ?? globalConfig.language,
};
2. API 回傳值的防護
對於後端 API,常見的情況是某些欄位在特定條件下不會回傳。前端若直接使用這些欄位,會產生 undefined 錯誤。?? 能在 UI 渲染前一次過填補缺失:
const { title, description, image } = articleData ?? {};
const imgSrc = image?.url ?? "/assets/placeholder.png";
3. 表單預設值
在表單編輯頁面,若使用者尚未填寫某些欄位,我們會顯示預設值或提示文字:
<input type="text" value={formData.name ?? ""} placeholder="請輸入姓名" />
4. Node.js 環境變數
在伺服器端,環境變數可能缺失,使用 ?? 可為其設定安全的 fallback:
const PORT = process.env.PORT ?? 3000;
const DB_URL = process.env.DB_URL ?? "mongodb://localhost:27017/app";
5. 多語系字串替換
當從 i18n 檔案取值時,若缺少某個鍵值,我們可以使用 ?? 給予預設語系:
const t = (key) => translations[currentLang][key] ?? translations["en"][key] ?? key;
總結
Null 合併運算子 ?? 為 JavaScript 提供了一個 專門針對 null 與 undefined 的簡潔語法,讓開發者能在不影響其他「假值」的情況下,安全地提供預設值。透過本篇文章,你應該已經了解:
??與傳統||的差異與適用時機- 如何在函式參數、API 回傳、解構賦值、React JSX 等情境中使用
?? ??=這個更進一步的賦值運算子,讓變數預設值的寫法更為簡潔- 常見的陷阱(運算子優先順序、與
||混用等)以及對應的最佳實踐
在實務開發中,善用 ?? 能大幅提升程式碼的可讀性與安全性,尤其在處理外部資料(API、環境變數、使用者輸入)時,更是一把避免 null / undefined 錯誤的好武器。建議在團隊的程式碼規範中加入 prefer‑nullish‑coalescing 的 lint 規則,讓每一位開發者都能統一使用這個現代化的運算子,寫出更乾淨、更可靠的 JavaScript 程式碼。
祝你在 JavaScript 的旅程中,玩得開心、寫得順手! 🎉