本文 AI 產出,尚未審核

JavaScript 課程:運算子(Operators)— Optional Chaining(?.)


簡介

在日常的前端開發中,我們常會碰到「物件屬性深層巢狀」的情況,例如 user.profile.address.city。如果其中任何一層為 nullundefined,直接存取會拋出 TypeError,導致程式中斷。過去只能透過多層 if 判斷或 && 短路運算子來防護,程式碼不僅冗長,也不易閱讀。

ECMAScript 2020 引入的 Optional Chaining(可選鏈)?.)正是為了解決這個痛點。它讓我們可以在一次存取時,同時檢查每一層是否為 nullundefined,若是則直接回傳 undefined,避免例外拋出。這項語法不僅提升開發效率,還能讓程式碼更簡潔、易於維護,對 初學者中階開發者 都相當友善。


核心概念

1. 為什麼需要 Optional Chaining?

在沒有 ?. 前,我們必須寫類似以下的防呆程式:

let city;
if (user && user.profile && user.profile.address) {
  city = user.profile.address.city;
}

如此寫法不僅冗長,還容易遺漏某層檢查,導致 隱蔽的錯誤。Optional Chaining 讓這段程式變成一行:

const city = user?.profile?.address?.city;   // 若任一層為 null/undefined,結果為 undefined

重點?. 只會在左側的值是 nullundefined 時停止求值,其他 falsy 值(如 0''false)不會被攔截。

2. 基本語法

用法 說明
obj?.prop objnull/undefined,回傳 undefined,否則回傳 obj.prop
obj?.[expr] 動態屬性存取,expr 為任意表達式
func?.(...args) 呼叫函式前先檢查函式是否為 null/undefined
new ctor?.(...args) 建構子呼叫前的檢查(ES2021)

範例

const config = {
  api: {
    endpoint: '/v1/data',
    timeout: 5000,
  },
};

const timeout = config?.api?.timeout;   // 5000
const retry = config?.api?.retry;       // undefined (不會拋錯)

3. 與其他運算子結合

Optional Chaining 常與 Nullish Coalescing (??) 搭配使用,提供「預設值」的功能:

const timeout = config?.api?.timeout ?? 3000; // 若 timeout 為 undefined/null,使用 3000

同時,也可以與 陣列的 ?. 結合,安全取得陣列元素或方法:

const first = users?.[0];          // 若 users 為 undefined,結果為 undefined
const length = users?.length;     // 若 users 為 undefined,結果為 undefined

4. 程式碼範例

以下提供 5 個實用範例,展示不同情境下的 Optional Chaining 用法。

範例 1:安全存取深層屬性

// 假設從 API 取得的資料可能缺少某些欄位
function getUserCity(user) {
  // 使用 ?. 直接取得 city,若任一層不存在則回傳 undefined
  return user?.profile?.address?.city;
}

// 測試
console.log(getUserCity({ profile: { address: { city: 'Taipei' } } })); // "Taipei"
console.log(getUserCity({ profile: null }));                           // undefined

範例 2:安全呼叫可能不存在的函式

const logger = {
  info: (msg) => console.log('INFO:', msg),
  // debug 可能在某些環境下不存在
};

function logDebug(message) {
  // 若 logger.debug 為 undefined,什麼都不執行
  logger.debug?.(message);
}

// 呼叫
logDebug('測試訊息'); // 沒有錯誤,什麼也不會印出
logger.info('一般訊息'); // "INFO: 一般訊息"

範例 3:動態屬性存取

const settings = {
  theme: 'dark',
  layout: 'grid',
};

function getSetting(key) {
  // 使用 obj?.[expr] 讓 key 可以是任何字串
  return settings?.[key];
}

console.log(getSetting('theme'));   // "dark"
console.log(getSetting('unknown')); // undefined

範例 4:安全呼叫建構子(ES2021)

class Person {
  constructor(name) {
    this.name = name;
  }
}

// 假設某些情況下不需要建立實例
function createPerson(name, shouldCreate) {
  // 若 shouldCreate 為 false,Person?. 會返回 undefined
  return shouldCreate ? new Person(name) : undefined;
}

const p1 = createPerson('Alice', true);
console.log(p1?.name); // "Alice"

const p2 = createPerson('Bob', false);
console.log(p2?.name); // undefined (不會拋錯)

範例 5:與 Nullish Coalescing 結合提供預設值

function getApiTimeout(config) {
  // 若 timeout 為 undefined 或 null,使用 2000 作為預設
  return config?.api?.timeout ?? 2000;
}

console.log(getApiTimeout({ api: { timeout: 5000 } })); // 5000
console.log(getApiTimeout({ api: {} }));               // 2000
console.log(getApiTimeout(undefined));                // 2000

常見陷阱與最佳實踐

陷阱 說明 解決方式
誤把 ?. 用在左值 obj?.prop = value 會產生 SyntaxError,因為 Optional Chaining 只能作為 右值(取值)或 呼叫 使用。 若需要條件賦值,先檢查再賦值: if (obj) obj.prop = value;
&& 混用造成重複檢查 同時使用 obj?.prop && obj.prop2obj?.prop 已經保證不為 null/undefined,再用 && 會多餘。 直接寫 obj?.prop?.prop2,保持語意一致。
對函式的 ?. 會返回 undefined func?.()funcundefined,整個表達式回傳 undefined,不會拋錯。若預期返回值必為布林或數字,需自行處理預設值。 使用 func?.() ?? defaultValue 來確保得到期望的類型。
不適用於 null/undefined 之外的 falsy 值 0''false 仍會被正常返回,若想把它們視為「無值」需額外判斷。 結合 ??obj?.prop ?? fallback
過度使用降低程式可讀性 雖然 ?. 讓程式碼變短,但過度鏈接(如 a?.b?.c?.d?.e) 可能讓讀者難以掌握資料結構。 盡量在 資料模型 明確的情況下使用,或先將中間結果存入變數。

最佳實踐

  1. 僅在需要防止 null/undefined 時使用,不要把它當成「萬能」的防呆工具。
  2. 配合 ?? 提供預設值,讓回傳結果更具可預測性。
  3. 在大型物件或 API 回傳資料時,先定義 TypeScript/Flow 型別,再結合 ?.,提升開發體驗。
  4. 保持鏈的深度在合理範圍(3~4 層),超過時考慮重構資料結構或使用函式抽象。

實際應用場景

  1. 前端 UI 渲染
    從後端取得的 JSON 可能缺少某些欄位,使用 user?.profile?.avatarUrl ?? '/default.png' 直接取得圖片路徑,避免 UI 因 undefined 而產生錯誤。

  2. React / Vue 組件 Props
    組件接收的 props 可能未傳入,props?.title?.toUpperCase() 可以安全呼叫字串方法,避免組件掛掉。

  3. 第三方套件 API
    某些套件在舊版不提供特定方法,使用 library?.newFeature?.() 讓程式在舊版環境仍能正常執行。

  4. Node.js 後端服務
    讀取環境變數或設定檔時,process.env?.DB?.HOST 可避免因未設定變數導致程式崩潰,並可結合 ?? 設定預設值。

  5. 測試與 Mock
    在單元測試中,透過 mockObj?.method?.() 只在 mock 提供該方法時才呼叫,減少測試程式碼的條件判斷。


總結

Optional Chaining(?.)是 ES2020 為 JavaScript 加入的強大運算子,專門解決深層屬性存取時的 null/undefined 安全問題。透過簡潔的語法,我們可以:

  • 減少繁雜的防呆程式if&&
  • 提升程式可讀性與維護性
  • 與 Nullish Coalescing (??)、解構賦值等語法自然結合

在實務開發中,合理使用 ?. 能讓前端 UI、React/Vue 組件、Node 後端服務等各種情境下的程式碼更健壯、更易於除錯。當然,開發者仍需留意其使用範圍與限制,避免過度鏈接或誤用於左值。掌握這項運算子,將是提升 JavaScript 開發效率的重要一步。祝你在寫程式的路上,玩得開心、寫得更好!