配了八年迭代器,今天才发现 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 跟上了再全面铺开。
评论区
登录后可评论。