本文 AI 產出,尚未審核

JavaScript 課程 – 運算子(Operators)

主題:Null 合併運算子(??)


簡介

在日常的 JavaScript 開發中,我們常會碰到「值可能是 nullundefined」的情況。傳統上,開發者會使用三元運算子、||(邏輯或)或大量的 if 判斷來提供預設值,寫起來既冗長又容易出錯。ECMAScript 2020(ES11)正式加入的 Null 合併運算子 ??,正是為了解決這類需求而設計的。

?? 只在左側運算元**真的為 nullundefined**時才會回傳右側的預設值,與 || 不同,|| 會把所有「假值」(0、''、false、NaN…) 都視為需要替換的情況。這讓程式碼更具可讀性,也降低了因為錯誤的「假值」判斷而導致的 bug。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,甚至延伸到真實專案中的應用場景,帶你完整掌握 Null 合併運算子的用法,讓你的 JavaScript 程式碼既簡潔又安全。


核心概念

1. Null 合併運算子的語法

let result = leftOperand ?? rightOperand;
  • leftOperandnullundefinedresult 會取得 rightOperand 的值。
  • 其他情況(包括 0、''、false、NaNresult 直接等於 leftOperand

重點?? 僅關注 nullish(nullish = null + undefined)而非所有「假值」。

2. 為什麼 ?? 不會取代 ||

運算子 判斷條件 會取代左側的情況
` `
?? nullish (null、undefined) 只在左側是 nullundefined 時回傳右側

範例比較

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 ?? {} 確保解構不會因 nullundefined 而拋出錯誤。

範例 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 ?? defaultobj?.prop ?? default 的差異不明 了解 ?. 先於 ??,先決定是否要存取屬性,再決定是否需要預設值。
在解構賦值時忘記提供空物件 const { x } = null; 會拋出錯誤 使用 options ?? {} 或 `options
在 TypeScript 中的型別推斷 `let v: string undefined = undefined; v = v ?? "fallback";仍保留undefined` 型別

最佳實踐

  1. 明確使用 ?? 取代 ||:只在需要保留 0、''、false 時才選擇 ??
  2. 搭配可選鏈 (?.):先確保屬性存在,再使用 ?? 供預設值,寫出「安全且簡潔」的單行程式。
  3. 使用 ??= 為變數設定預設:尤其在物件初始化或設定合併時,可大幅減少冗長的 if 判斷。
  4. 保持一致的風格:團隊若決定採用 ??,請在程式碼規範(如 ESLint)中加入 prefer-nullish-coalescing 規則,避免混用 || 產生不一致的行為。
  5. 測試「nullish」情況:寫單元測試時,特別加入 nullundefined 以及 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 提供了一個 專門針對 nullundefined 的簡潔語法,讓開發者能在不影響其他「假值」的情況下,安全地提供預設值。透過本篇文章,你應該已經了解:

  • ?? 與傳統 || 的差異與適用時機
  • 如何在函式參數、API 回傳、解構賦值、React JSX 等情境中使用 ??
  • ??= 這個更進一步的賦值運算子,讓變數預設值的寫法更為簡潔
  • 常見的陷阱(運算子優先順序、與 || 混用等)以及對應的最佳實踐

在實務開發中,善用 ?? 能大幅提升程式碼的可讀性與安全性,尤其在處理外部資料(API、環境變數、使用者輸入)時,更是一把避免 null / undefined 錯誤的好武器。建議在團隊的程式碼規範中加入 prefer‑nullish‑coalescing 的 lint 規則,讓每一位開發者都能統一使用這個現代化的運算子,寫出更乾淨、更可靠的 JavaScript 程式碼。

祝你在 JavaScript 的旅程中,玩得開心、寫得順手! 🎉