你以为 for…of 已经是处理迭代器的最优方式了?今天 chunks() 证明了这件事根本不是那么回事

你以为 for…of 已经是处理迭代器的最优方式了?今天 chunks() 证明了这件事根本不是那么回事

配了三年数据处理,每次拿到一个 Iterator,第一件事永远是先 .toArray()——分批要数组、窗口要数组、成员判断要数组、拼成字符串也要数组。迭代器是懒的,你把它变成数组的那一刻,全量加载进内存,懒的优势全没了。

Firefox 154 把这件事彻底原生化了。


四个新方法,对应四个真实痛点

Firefox 154 给 Iterator.prototype 加了四个方法,均为 TC39 Stage 3 提案:

Iterator.prototype.chunks(chunkSize) — 把迭代器切成固定大小的批次,每次 yield 一个数组。不重叠。

Iterator.prototype.windows(windowSize) — 滑动窗口,每次 yield 一个数组,可以配置是否保留不满的尾部(undersized: "allow-partial")。

Iterator.prototype.includes(value) — 判断迭代器是否包含某个值,无需转数组。

Iterator.prototype.join(separator) — 把迭代器所有元素拼成字符串,用分隔符连接,无需转数组。


懒迭代器直接操作,内存占用是原来的零头

这套方法的核心价值在于:它们操作的依然是迭代器,不是数组。

// 旧方式:先转数组,全量加载
const values = [...largeIterator];
const batches = [];
for (let i = 0; i < values.length; i += 100) {
  batches.push(values.slice(i, i + 100));
}

// 新方式:直接在迭代器上分批,内存零额外开销
for await (const batch of iterator.chunks(100)) {
  // batch 是最多 100 个元素的数组
  await processBatch(batch);
}

100 万条记录的分批处理,旧方式需要先把 100 万条全部转成数组再处理;新方式可以边读边批,边批边处理,内存峰值从 O(n) 降到 O(batch_size)。


chunks() vs windows():不重叠 vs 重叠

两者的区别在于是否重叠:

const data = [1, 2, 3, 4, 5];

// chunks() — 不重叠的固定批次
[...data.values().chunks(2)]
// → [[1, 2], [3, 4], [5]]

// windows() — 滑动窗口
[...data.values().windows(3)]
// → [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

// windows() 保留不满的尾部
[...data.values().windows(3, 'allow-partial')]
// → [[1, 2, 3], [2, 3, 4], [3, 4, 5], [5]]

chunks() 适合批处理、批量写入、分页请求;windows() 适合滑动平均、趋势分析、连续帧处理。


includes() 和 join():原来每次都要先 toArray()

// includes() — 无需转数组
const hasTarget = myIterator.includes(searchValue);
// 原来:const hasTarget = [...myIterator].includes(searchValue);

// join() — 无需转数组
const str = myIterator.join(', ');
// 原来:const str = [...myIterator].join(', ');

对于超大迭代器,这两个方法可以短路(early exit):includes() 找到就停,join() 拼完就停,不会多做一次全量加载。


适用场景

大文件/流式数据处理:日志分析、CSV 解析、数据库游标,每次读一批,不需要全量加载到内存。

分页 API 调用:数据源是生成器,分批发送请求,每批独立重试。

滑动窗口计算:移动平均、滚动聚合,windowSize 决定窗口大小,allow-partial 控制尾部处理策略。

Worker / Service Worker 场景:主线程和 Worker 之间传迭代器比传数组更轻量,现在可以直接在 Worker 内部做分批。


浏览器支持现状

Firefox 154(2026-08-18)首发,Chrome/Safari 暂未支持。Blink 正在实现中(tracked by issues.chromium.org/issues/504663595)。

// 渐进增强写法
const chunked = iterator.chunks
  ? iterator.chunks(100)
  : Array.from(iterator).reduce((batches, item, i) => {
      const group = Math.floor(i / 100);
      (batches[group] = batches[group] || []).push(item);
      return batches;
    }, []);

迁移路径

第一步:把项目中 Array.from(iterator)[...iterator] 的地方扫一遍,这三个操作是最高频替换目标:分批(→ .chunks())、成员判断(→ .includes())、拼接(→ .join())。

第二步:检查生成器/迭代器的调用方,确认数据源足够大,再用懒操作替代数组化——小数据量转数组没毛病,大数据量再动手。

第三步:加 @supports 或 feature detection,等 Chrome/Safari 跟上来全量切。


总结一句话:迭代器是懒的,你把它变数组的那一刻就变勤快了——Firefox 154 把这件事还给了语言本身。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 16 阅读