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) |
| 运算方式 | 创建中间数组 | 逐项运算 |
| 典型用途 | 小至中型数据处理 | 大数据或流处理 |