跳轉到

2018:ECMAScript 2018——非同步迭代#

事件#

2018年6月,ECMAScript 2018(ES9) 發佈,主軸是非同步迭代、物件其餘/展開屬性與正則表達式大升級。

新語法與特性#

1. for await...of 與非同步迭代器#

  • 為何加入:for...of(ES6)只能迭代同步集合;串流處理(逐行讀檔、分頁 API)需要迭代「逐步產生的非同步值」。
  • 理論原因:擴充迭代器協議——Symbol.asyncIterator 讓迭代協議支援 Promise,是協程理論的延伸。
  • 實用原因:Node 的串流(streams)與分頁抓取可用 for await (const chunk of stream) 自然表達。
  • 彌補缺陷:非同步序列無法用迴圈迭代的缺陷。

2. 物件的其餘/展開屬性(...)#

  • 為何加入:陣列的展開/其餘在 ES6 已有,物件版本一直缺席(要用 Object.assign 複製)。
  • 理論原因:補齊語法對稱性——陣列與物件應有一致的展開語義。
  • 實用原因:React/Redux 時代「不可變更新」是核心操作,{...state, count: state.count + 1} 成為標準寫法。
  • 彌補缺陷:物件複製要靠 Object.assign 的冗長與淺拷貝陷阱。

3. Promise.prototype.finally#

  • 為何加入:Promise 鏈的成功與失敗要分別處理,「無論成敗都要做的事」(關閉 loading、釋放資源)要寫兩遍。
  • 理論原因:對應 try/catch/finally 的 finally 語義——資源清理的正統位置。
  • 實用原因:UI 的 loading 狀態切換是每個請求的必做項。
  • 彌補缺陷:Promise 缺少 finally 等價物的不完整。

4. 正則表達式大升級#

  • 命名捕獲群組 (?<year>\d{4}):以前要用數字索引(match[1]),命名群組可讀性大增,借鑑 Python/.NET 的正則語法。
  • Lookbehind 斷言 (?<=...)、(?<!...):補齊 lookahead 的對稱缺口。
  • s 旗標(dotAll):. 能匹配換行,以前要靠 [\s\S] 駭客寫法。
  • Unicode 屬性跳脫 \p{Script=Greek}:國際化文字處理的標準路徑。
  • 彌補缺陷:ES3 正則(1999)20年來的語法缺口總補齊。

理論總結#

ES2018 的主軸是「補齊對稱性」:物件補上陣列已有的展開/其餘、正則補上各語言早已有的命名群組與 lookbehind、Promise 補上 try/finally 的等價物。

實用總結#

物件展開運算子成為不可變資料範式(React/Redux)的語言級支柱,是使用頻率最高的 ES2018 特性。

相關條目#

程式範例#

1. for await...of:逐步迭代非同步序列#

// 逐行讀取串流(Node streams / 分頁 API)
async function* lines(path) {
  const stream = fs.createReadStream(path);
  for await (const chunk of stream) {   // ES2018:迭代非同步值
    yield chunk.toString();
  }
}
for await (const line of lines("/var/log/app.log")) {
  console.log(line);
}

2. 物件展開/其餘:不可變更新的語言支柱#

// ES2017 之前:Object.assign 複製(冗長)
const next = Object.assign({}, state, { count: state.count + 1 });

// ES2018:物件展開(React/Redux 標準寫法)
const next = { ...state, count: state.count + 1 };

// 其餘屬性:解構時收攏剩下的
const { password, ...safeUser } = user;   // 移除密碼欄位再回傳

3. Promise.finally:無論成敗的清理#

showLoading();
fetch("/api")
  .then((res) => res.json())
  .then(render)
  .catch(showError)
  .finally(hideLoading);   // 以前要在 then 和 catch 各寫一次 hideLoading()

4. 正則大升級#

// 命名捕獲群組:可讀性大增
"2026-10-08".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/).groups;
// { year: "2026", month: "10", day: "08" }——以前要用 match[1], match[2]

// Lookbehind:補齊 lookahead 的對稱
"$100".match(/(?<=\$)\d+/);     // ["100"]——前面是 $ 才匹配
"a1 b2".match(/\d+(?!px)/);     // lookahead 對照

// s 旗標(dotAll)
"line1\nline2".match(/line1.line2/s);  // 匹配成功——以前 . 不吃換行

// Unicode 屬性跳脫
"日本語".match(/\p{Script=Japanese}/u);  // 國際化文字處理