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——通通一致