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

延伸阅读