跳轉到

2020:ECMAScript 2020——可選鏈與空值合併#

事件#

2020年6月,ECMAScript 2020(ES11) 發佈。?. 可選鏈與 ?? 空值合併是繼 ES6 之後使用頻率提升最快的語法,直擊 JavaScript 最常見的執行期錯誤。

新語法與特性#

1. 可選鏈(Optional Chaining ?.)#

  • 為何加入:存取深層巢狀屬性(user.address.city.zip)時,任何一層是 null/undefined 就拋 TypeError——這是 JavaScript 生產環境最常見的執行期錯誤("Cannot read property 'x' of undefined")。以前要寫 user && user.address && user.address.city && ... 的防禦鏈。
  • 理論原因:源自 C#(2007)的 ?.、Swift 的 optional chaining、CoffeeScript 的 ?.——「空安全導航」已是其他語言的成熟設計。
  • 實用原因:API 回傳的 JSON 資料結構不確定(欄位可能缺失),防禦性存取是日常。
  • 彌補缺陷:深層存取的防禦鏈樣板與最常見的 TypeError。

2. 空值合併(Nullish Coalescing ??)#

  • 為何加入:|| 是假值(falsy)判斷——0、''、false 都會被當成「沒有值」而落到預設值,這是著名陷阱:count || 10 在 count = 0 時錯誤地回傳 10。
  • 理論原因:?? 只在真正的 null/undefined 時取右值,語義精確;借鑑 C# 的 ?? 與 SQL 的 COALESCE。
  • 實用原因:設定檔、參數預設值中,「0 和空字串是合法值」的場景遍地都是。
  • 彌補缺陷:|| 的假值陷阱——JavaScript 最經典的語義陷阱之一。

3. BigInt#

  • 為何加入:Number 用 IEEE 754 雙精度浮點,安全整數上限是 2^53-1(Number.MAX_SAFE_INTEGER),超過就失真。
  • 理論原因:任意精度整數是數值計算的基礎(Python、Ruby 早已內建);BigInt 為新原語型別(第8種型別),以 n 字尾字面量(9007199254740993n)表示。
  • 實用原因:金融計算(不允許浮點誤差)、加密(大數運算)、區塊鏈(64位元 ID)。
  • 彌補缺陷:整數精度上限與浮點誤差。

4. Promise.allSettled#

  • 為何加入:Promise.all 是「全有或全無」——任一失敗立即 reject,無法取得其他成功的結果。
  • 實用原因:批次請求(同時抓多個 API)希望「成功的用成功值、失敗的記錄錯誤」。
  • 彌補缺陷:Promise 組合子的全有或全無語義。

5. globalThis#

  • 為何加入:全域物件在瀏覽器是 window、Node 是 global、Worker 是 self,跨環境存取全域要寫環境偵測駭客碼。
  • 彌補缺陷:全域物件名稱不一致的跨環境缺陷。

6. 動態 import(import())#

  • 為何加入:ES6 的 import 是靜態宣告,無法在執行期決定載入哪個模組。
  • 實用原因:程式碼分割(code splitting)與按需載入——打包工具(webpack、Vite)的路由級懶載入的語言基礎。
  • 彌補缺陷:模組只能靜態載入、無法條件載入。

7. String.prototype.matchAll#

  • 為何加入:配合 g 旗標取出所有匹配(含群組),以前要手寫 exec 迴圈。
  • 彌補缺陷:全域匹配迭代工具缺失。

理論總結#

ES2020 的主軸是「空安全與精度」:?. 與 ?? 直接消滅兩個最常見的執行期錯誤來源;BigInt 補上數值型別的最後一塊。兩者都借鑑其他語言的成熟設計(C#、Swift、Python),不發明新概念。

實用總結#

?. 與 ?? 在現代程式碼中的出現頻率僅次於 ES6 的 let/const 與箭頭函數。TypeScript(2012,見 2012-TypeScript問世)早在標準化前就支援這兩個語法,再次驗證「社群先試、標準後收」模式。

相關條目#

程式範例#

1. 可選鏈 ?.:消滅最常見的執行期 TypeError#

// ES2019 之前:防禦鏈樣板 + 生產環境最常見的錯誤
const zip = user && user.address && user.address.city && user.address.city.zip;
// "Cannot read property 'zip' of undefined"——JS 生產環境第一大錯誤

// ES2020:一行
const zip = user?.address?.city?.zip;   // 任何一層是 null/undefined → 整體 undefined

// 方法與陣列也適用
const name = user.getName?.();          // getName 不存在 → undefined 而非 TypeError
const first = list?.[0];                // 陣列索引防禦

2. 空值合併 ??:消滅 || 的假值陷阱#

// 著名陷阱:0 和空字串是「合法值」,卻被 || 當成沒有值
const timeout = config.timeout || 3000;   // config.timeout = 0 時錯誤回傳 3000!
const name = user.name || "anonymous";    // user.name = "" 時錯誤被覆蓋!

// ES2020:?? 只在真正的 null/undefined 時取右值
const timeout = config.timeout ?? 3000;   // 0 就是 0,正確
const name = user.name ?? "anonymous";    // "" 就是 "",正確

// 與 ?. 的黃金組合
const zip = user?.address?.zip ?? "N/A";

3. BigInt:突破 2^53 精度上限#

// Number 的精度缺陷
Number.MAX_SAFE_INTEGER;              // 9007199254740991(2^53-1)
9007199254740993 === 9007199254740992; // true——大數失真!

// ES2020:BigInt(第8種型別)
9007199254740993n;                    // 精確
2n ** 64n;                            // 18446744073709551616n
9007199254740993n + 1n;               // 9007199254740994n
// 應用:金融計算、加密大數、區塊鏈 64位元 ID

4. Promise.allSettled:批次請求的成功/失敗並存#

// Promise.all:全有或全無——一個失敗全部丟失
const results = await Promise.all([fetchA(), fetchB(), fetchC()]);  // B 失敗 → 全部沒了

// ES2020:allSettled——成功的用成功值、失敗的記錄
const settled = await Promise.allSettled([fetchA(), fetchB(), fetchC()]);
settled.forEach((r) =>
  r.status === "fulfilled" ? use(r.value) : logError(r.reason)
);

5. 動態 import 與 globalThis#

// 動態 import:執行期決定載入(程式碼分割/懶載入的語言基礎)
const mod = await import("./heavy-chart.js");   // 只在需要時載入
if (route === "/chart") { const chart = await import("./chart"); }

// globalThis:跨環境存取全域
globalThis.fetch;   // 瀏覽器=window、Node=global、Worker=self——通通一致