JavaScript Array Lazy Evaluation Through Iterator Helper
JavaScript 陣列惰性求值透過 iterator helper
前言
先前提到 JavaScript 的 Iterator Protocol:什麼讓 JavaScript 資料結構能夠被迭代? 了解 Iterator Protocol 與 Generator ,而陣列正是基於 iterator 實做,自然受惠於近期推出的 iterator helper 來進行惰性求值。
及早求值的陣列方法有什麼不足?
舉陣列方法如 map
、filter
、slice
能以直覺的方式鏈式處理資料,但它們屬於立即執行(eager evaluation)每一次操作都會建立新的陣列。對於小型資料集來說影響不大,但若處理大量資料或串流時,這種重複建立與複製的成本會相當可觀。
const arr = [1, 2, 3].map(x => x * 2).filter(x => x > 2);// map 和 filter 都會馬上建立新陣列
惰性求值的 Iterator Helper 有什麼好處?
更好的效率與空間運用
在新的 Iterator Helpers 提案 中,陣列可以透過 .values()
取得 iterator,並以惰性求值(lazy evaluation)的方式進行運算。這代表每個元素只在「被消耗」時才會真正被處理,不會提前建立中間結果。
const iter = [1, 2, 3, 4, 5].values() .map(x => x * 2) .filter(x => x > 5)
console.log([...iter]); // [ 6, 8, 10 ]// 例如展開 [...] 或用 for...of)時才逐步執行
Iterator Helpers 的每個方法都只回傳新的 iterator, 不會產生中間陣列,也不會預先計算所有值,最終可以透過 toArray 或展開等方式簡單轉換成陣列。
惰性求值達成更少的運算
舉例要從 10 萬筆資料中找到前 5 個超過 50% 折扣的商品:
const topDiscounted = products .filter(p => p.discount > 0.5) // 遍歷所有 10 萬筆 .slice(0, 5); // 再取前 5 筆
即使理論上只要找到 5 個滿足條件的商品就能停止運算,但及早求值使我們仍須透過遍歷完所有資料來計算,而使用惰性求值的思維通常能大幅縮減所需的運算:
const topDiscounted = products .values() // 轉成 iterator .filter(p => p.discount > 0.5) // 逐步篩選 .take(5); // 找到前 5 個滿足條件的商品即停止
console.log([...topDiscounted]);
總結
比較項 | Array 方法 | Iterator Helpers |
---|---|---|
資料結構 | 陣列(eager) | 迭代器(lazy) |
運算方式 | 建立中間陣列 | 逐項運算 |
典型用途 | 小至中型資料處理 | 大資料或串流處理 |