配了八年迭代器,今天才发现 join() 从来不是数组专属的

写过前端的人都踩过这个坑——从数据库拉了一条游标回来,想把它拼成逗号分隔的字符串,脑子里第一个念头是 .toArray().join(',')

这口气咽得下去,但总觉得哪里不对:明明是个惰性求值的生成器,硬生生给灌成一个数组,就为了调用一个 join。万一这条游标里有十万条数据,那 toArray() 先把十万条全塞进内存,再 join ——这中间白白多耗的内存谁来买单?

今天这件事被 Iterator.prototype.join 彻底原生化了。


join() 是什么

Iterator.join() 是 ES2026 引入的迭代器辅助方法,签名和 Array.prototype.join() 一模一样:接受一个分隔符参数(可选,默认为逗号),返回把所有迭代器元素拼成的字符串。最大的区别在于:它直接在迭代器上调用,不要求你先把数据倒腾到数组里。

function* fibonacci() {
  let current = 1, next = 1;
  while (true) {
    yield current;
    [current, next] = [next, current + next];
  }
}

// 惰性:只取前5个,不存在中间数组
fibonacci().take(5).join();          // "1,1,2,3,5"
fibonacci().take(5).join(' - ');     // "1 - 1 - 2 - 3 - 5"

.take() 也是迭代器辅助方法,取前 N 个元素后自动停止。两者组合,整个 pipeline 全程惰性,没有任何中间数据结构被创建。


为什么以前没人做

这其实是个被反复提过的需求。Python、Java、Kotlin、C# 早就各自有类似实现,JS 社区在 GitHub 上搜 [...map.keys()].join() 能搜出两千多条,说明这个模式在实践中需求量不小。

但 JS 迭代器辅助方法很长一段时间里只有 .map()、.filter()、.take() 这几个,唯独缺了一个做字符串拼接的。根本原因在于 join 需要”看完全部元素才能吐出第一个”,这和其他惰性方法(map、filter 都是逐个产出)的风格不一致,所以 TC39 讨论了很久才最终拍板。


三个真实场景

场景一:数据库大字段拼接

数据库游标来了,直接在游标上 join,不用担心 toArray 把整个结果集倒进内存。

// 之前:全量灌入数组
const tags = yield* db.queryTags(productId);
const tagStr = [...tags].join('、');

// 现在:全程惰性
const tagStr = db.queryTags(productId).join('、');

场景二:日志分析管道

日志流经过 filter、map 若干步处理,最后输出为一行文本。

const line = logs
  .filter(entry => entry.level === 'ERROR')
  .map(entry => `${entry.time}:${entry.msg}`)
  .join('n');
// 中间全程是流,不存在中间数组

场景三:国际化文案拼接

多语言词表从 JSON 加载后是个 Map,想将 keys 拼成逗号分隔的字符串检查覆盖率。

const unusedKeys = i18nMap
  .keys()
  .filter(k => !usedKeys.has(k))
  .join(', ');

兼容性现状

浏览器 支持版本
Firefox 154(完整支持)
Chrome 153(预览支持)
Edge 153(预览支持)
Bun 1.4(完整支持)
Safari / Node.js 暂无

Firefox 154 已完整支持,Chrome/Edge 在 153 预览版中可用,Bun 在 1.4 跟上了。Safari 和 Node.js 暂时缺席,生产环境使用时建议加一层 feature detection:

const str = Iterator.prototype.join
  ? iterator.join('、')
  : [...iterator].join('、');

注意这个坑

和大多数迭代器辅助方法不同,join() 不是惰性的——它必须消费完整个迭代器才能吐出第一个字符。这意味着它不能用于无限迭代器:

// 永远跑不完
fibonacci().join(' - '); // 死循环,take() 是必须的

// 取有限数量再 join
fibonacci().take(10).join(' - '); // 正确用法

下一步

如果你的项目还在用 [...iter].join() 或者 .toArray().join(),现在可以开始迁移了。迭代器辅助方法全家桶现在已经比较完整:.map()、.filter()、.take()、.drop()、.reduce()、.some()、.forEach() 加上 .join(),常见的数组操作基本都能在迭代器上直接跑,不需要中间数组。用一下 feature detection,先在非关键路径上跑起来,等 Safari 和 Node.js 跟上了再全面铺开。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 305 阅读