JavaScript Array Lazy Evaluation Through Iterator Helper

JavaScript 陣列惰性求值透過 iterator helper

前言

先前提到 JavaScript 的 Iterator Protocol:什麼讓 JavaScript 資料結構能夠被迭代? 了解 Iterator Protocol 與 Generator ,而陣列正是基於 iterator 實做,自然受惠於近期推出的 iterator helper🔗 來進行惰性求值。

及早求值的陣列方法有什麼不足?

舉陣列方法如 mapfilterslice 能以直覺的方式鏈式處理資料,但它們屬於立即執行(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)
運算方式建立中間陣列逐項運算
典型用途小至中型資料處理大資料或串流處理

延伸閱讀