写过 JS 的人都踩过这个坑——迭代器每次想查某个值在不在,都要先全吐成数组。今天这件事被一个方法彻底原生化了
写过 JS 的人都踩过这个坑——迭代器每次想查某个值在不在,都要先全吐成数组才能调 includes。生成器跑了几百万条数据,好不容易定位到问题了,结果为了查一个值要把整个序列重新跑一遍;流式读取文件读到一半想判断有没有某个关键词,只能先 buffer.toArray() 再 includes。这不是功能没实现,是语言层面缺一个方法。
Chrome 154 和 Firefox 154 现在把这个坑填上了——Iterator.prototype.includes。
function* gen() {
yield 1;
yield 3;
yield 5;
}
const it = gen();
it.includes(3); // true,找到即停,迭代器停在 3 之后
it.includes(2); // false,迭代器停在末尾
和 Array.prototype.includes 用的是同一个比较算法:SameValueZero,和 === 基本一样,唯一的区别是 NaN 会等于自身。所以 [NaN].includes(NaN) 和 Iterator.from([NaN]).includes(NaN) 都是 true。
配合 .drop() 从指定位置开始查,不需要 second 参数:
function* fibonacci() {
yield* [1, 1, 2, 3, 5, 8, 13];
}
fibonacci().drop(3).includes(5); // true
fibonacci().includes(13); // true
fibonacci().drop(5).includes(2); // false
这里有个容易踩的坑:includes 是消费性的。调用 includes 的同时迭代器也在往前走,找到就停,找不到就耗尽。如果你的迭代器只能跑一次,别在调试的时候随手调了个 includes 然后继续用同一个迭代器——它已经被消费掉一部分了。
另一个需要注意的:迭代器是懒求值的,找到目标值后立刻停止,不会把整个序列跑完。对于无限生成器这个区别很关键:
function* infinite() {
let i = 0;
while (true) yield i++;
}
// 检查 42 有没有出现过——只跑 43 次就停了,不会死循环
infinite().includes(42); // true
Safari 目前还在开发中,Stage 3 的规格已经冻结,等 Safari 跟进后主流浏览器就全支持了。如果需要现在就在生产环境用,core-js 和 polyfill.io 都有 polyfill。
下一步:打开浏览器的 console,对着你手头那些返回生成器的函数跑一遍 includes,看看有没有以前为了查一个值就全吐成数组的代码——现在可以把它改掉了。
评论区
登录后可评论。