写过 JS 的人都踩过这个坑——迭代器每次想查某个值在不在,都要先全吐成数组。今天这件事被一个方法彻底原生化了

写过 JS 的人都踩过这个坑——迭代器每次想查某个值在不在,都要先全吐成数组才能调 includes。生成器跑了几百万条数据,好不容易定位到问题了,结果为了查一个值要把整个序列重新跑一遍;流式读取文件读到一半想判断有没有某个关键词,只能先 buffer.toArray() 再 includes。这不是功能没实现,是语言层面缺一个方法。

Chrome 154Firefox 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,看看有没有以前为了查一个值就全吐成数组的代码——现在可以把它改掉了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 14 阅读