跳轉到

2021:ECMAScript 2021——邏輯賦值運算子#

事件#

2021年6月,ECMAScript 2021(ES12) 發佈,主軸是邏輯賦值運算子與字串/數字的小幅精緻化。

新語法與特性#

1. 邏輯賦值運算子(||=、&&=、??=)#

  • 為何加入:「若無值則設預設」的常見模式以前要寫 if (!opts.timeout) opts.timeout = 3000 或 opts.timeout = opts.timeout || 3000(後者有假值陷阱)。
  • 理論原因:複合賦值運算子族譜的補齊(+=、-= 早已存在,邏輯版本缺席);??= 與 ES2020 的 ??(真 nullish 語義)成對。
  • 實用原因:預設值初始化(??=)、條件執行(&&=)、後備值(||=)是設定物件處理的日常操作。
  • 彌補缺陷:預設值設定的樣板程式碼,以及 || 假值陷阱的殘存場景。

2. String.prototype.replaceAll#

  • 為何加入:replace 搭配字串只替換第一個匹配——這是著名陷阱(要全域替換必須傳 regex 加 g 旗標,而 regex 的特殊字元還要跳脫)。
  • 理論原因:語義明確化——「replace all」用名字說清楚,不需要「字串參數=只換一個、regex 參數=看旗標」的隱式規則。
  • 實用原因:模板替換、路徑轉換等全域替換是常見需求。
  • 彌補缺陷:replace 的隱式「只換第一個」陷阱。

3. 數字分隔符(Numeric Separators,1_000_000)#

  • 為何加入:長數字(時間戳、金額、位元遮罩 0b1010_0101)無法一眼看出位數,數錯位數是常見 bug。
  • 理論原因:借鑑 Python、Swift、Rust 的 _ 分隔符傳統。
  • 彌補缺陷:長數字常數的可讀性。

4. Promise.any 與 AggregateError#

  • 為何加入:Promise 組合子的最後一塊拼圖——all(全成)、race(最先)、allSettled(全計)都有了,獨缺「任一成功即成」。
  • 理論原因:any 短路於第一個 fulfilled,全部失敗才 reject(拋 AggregateError 聚合所有錯誤)。
  • 實用原因:多 CDN 抓同一資源(哪個快用哪個)、多節點容錯查詢。
  • 彌補缺陷:Promise 組合子族譜的不完整——至此 all/race/allSettled/any 四件套補齊。

5. WeakRef 與 FinalizationRegistry#

  • 為何加入:弱引用(weak reference)讓物件可在不被強引用的情況下被垃圾回收,用於快取、物件關聯表等場景。
  • 理論原因:借鑑 Java 的 WeakReference;FinalizationRegistry 對應 finalizer 概念。
  • 彌補缺陷:無法參與垃圾回收生命週期的缺陷(附帶說明:規範明確警告不應依賴 finalizer 做清理,這是語言「提供了但建議少用」的罕見案例)。

理論總結#

ES2021 的主軸是「族譜補齊」:賦值運算子補齊邏輯版本、Promise 補齊 any、字串補齊 replaceAll。每一項都消除一個歷史陷阱(replace 只換一個、|| 假值)。

實用總結#

??= 與 replaceAll 迅速成為高頻語法;Promise.any 讓「多來源競速容錯」成為一行程式碼。

相關條目#

程式範例#

1. 邏輯賦值運算子:預設值的精簡寫法#

// ES2020 之前:樣板
if (!options.timeout) options.timeout = 3000;
options.timeout = options.timeout || 3000;   // 有假值陷阱!

// ES2021:三個運算子
options.timeout ??= 3000;   // nullish 才設——0 保留,最精確
options.timeout ||= 3000;   // falsy 才設——0 被覆蓋
options.debug &&= logger(); // 為真才執行賦值

2. replaceAll:消滅「只換第一個」陷阱#

// 著名陷阱:字串參數只替換第一個
"a.b.c".replace(".", "-");   // "a-b.c"——只有第一個被換!

// 要全域替換必須 regex + g 旗標(特殊字元還要跳脫)
"a.b.c".replace(/\./g, "-"); // "a-b-c"
"5.5".replace(".".replace(...), "-");  // 噩夢

// ES2021:replaceAll——名字說清楚
"a.b.c".replaceAll(".", "-"); // "a-b-c"

3. 數字分隔符:長常數可讀性#

// ES2020 之前:數錯位數是常見 bug
const ms = 86400000;          // 一天是多少毫秒?數不過來
const mask = 0b10100101;

// ES2021:_ 分隔符
const ms = 86_400_000;        // 86.4M 一眼看出
const mask = 0b1010_0101;     // 位元組分組
const cc = 1234_5678_9012_3456;  // 信用卡號

4. Promise.any:四件套補齊#

// all/race/allSettled 都有了,獨缺「任一成功」
// 多 CDN 競速:哪個快用哪個,全失敗才報錯
try {
  const data = await Promise.any([cdn1(), cdn2(), cdn3()]);
  render(data);
} catch (e) {
  e instanceof AggregateError;   // 全部失敗 → 聚合所有錯誤
  e.errors;                      // [錯誤1, 錯誤2, 錯誤3]
}

// 對照組:Promise.race——最先 settle 的贏(即使它是失敗)
const data = await Promise.race([cdn1(), cdn2(), cdn3()]);  // cdn1 最先失敗也回傳失敗!

5. WeakRef:垃圾回收生命週期#

let cache = new WeakRef(makeBigObject());   // 弱引用:不被強引用就可回收
cache.deref();  // 若已被回收 → undefined