配了三年 JavaScript,每次想判断一个惰性迭代器里有没有某个值,都要先调一遍 toArray()——今天 Chrome 154 把这件事从根上原生化了
惰性迭代器「查是否存在」一直是残缺的——你想判断一个无限斐波那契序列里有没有某个数,在 Chrome 154 之前只能先 .toArray() 转数组,再调 .includes(),这个操作既破坏惰性又浪费内存。今天 Iterator.prototype.includes 终于进 Chrome 154,.includes() 可以直接在惰性迭代器上跑了,找到就停,不算多余的数。
痛点:Generator 查是否存在,只能先全部物化
Generator 跑起来不用不占内存,这是它和数组的本质区别:
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
这个函数跑一万年也不会爆内存,因为值是按需产生的。但问题来了——你想问它里面有没有某个数?
在 Chrome 154 之前,没有好办法。你只能:
// 方案一:全部算出来再查(破坏惰性)
[...fibonacci()].includes(89)
// 方案二:自己写循环(每次都要重新写)
function hasFibonacci(n) {
for (const v of fibonacci()) {
if (v === n) return true;
if (v > n) return false;
}
}
方案一会把无限序列全部物化到内存,方案二又臭又长每次都要重复。Iterator.includes 补上了这最后一块短板。
核心:.includes() 直接在惰性迭代器上跑,找到就停
// 判断一个无限斐波那契数列里有没有 89
fibonacci().includes(89); // true,找到就停,后面的数一个都没算
// 判断有没有 10
fibonacci().includes(10); // false,第一个比10大的数出现就停了
.includes() 的行为:惰性前进,遇到目标立即返回 true,迭代器耗尽都没找到则返回 false。
这意味着:
- 不等所有值都算出来才判断 — 找到就走
- 不创建中间数组 — 全程一个值一个值地拉
- 兼容 SameValueZero — NaN 能查到,和 Array.prototype.includes 行为一致
function* gen() {
yield 1;
yield 3;
yield NaN;
}
gen().includes(NaN); // true(数组的 includes 也行)
gen().includes(1); // true
gen().includes(2); // false
// 配合 .drop() 跳过前面的值
gen().drop(1).includes(1); // false(第一个是3)
它和 .some() 的区别在于:.some() 需要一个回调函数来表达意图,.includes() 是直接比较值,语义更接近 Array.includes。
场景一:Generator 里判断有没有某个值,不污染原迭代器
function* primeGenerator() {
let n = 2;
while (true) {
if (isPrime(n)) yield n;
n++;
}
}
// 判断 1000 以内有没有某个质数——找到就停,不会无限跑
primeGenerator()
.take(168) // 取前168个质数(1000以内)
.includes(997); // true,第168个质数就是997,一路找到这里才停
.take(168) 是边界,防止无限序列真的无限跑。.includes() 配合 .take() 使用是典型模式。
场景二:无限序列「有没有」的早期退出
// 判断无限计数序列里有没有 42
function* naturals(start = 0) {
while (true) yield start++;
}
naturals().includes(42); // true,第42次迭代就返回了
在无限序列上调用 .includes(),只要目标值在序列里,最多只会迭代到目标值所在位置就停,不会真的无限跑下去。
场景三:和 Iterator.zip / .filter() 组合用
const zipped = Iterator.zip([fibonacci(), naturals()]);
// zipped 是一个迭代器,每次 yield: [fib数字, naturals数字]
// 判断有没有 [55, 55] 这个配对
zipped.includes([55, 55]); // true
Iterator 家族的方法可以链式组合,.includes() 是最后一个常用的检查点。
兼容性:Chrome 154 Beta,今天刚上线
Chrome 154 在 2026年9月2日 进入 Beta,9月9日稳定。关键里程碑:
| 引擎 | 状态 |
|---|---|
| Chrome/V8 | 154 Beta(今天) |
| Firefox/SpiderMonkey | 已稳定(bugzilla 2025773) |
| Safari/JavaScriptCore | 开发中(WebKit #310698) |
| Node.js | 预计 Node.js 24 |
| core-js | 3.50.0 已 polyfill |
Chrome 154 正式上线后,全球 Chromium 覆盖率就能用上。如果要现在就上,有 core-js polyfill 可以兜底:
import "core-js/actual/iterator/includes";
// 之后任意迭代器都能用 .includes()
三个坑
1. 找不到不会全部迭代完才能判断
只要迭代器的值有上界(比如自然数从0开始),.includes() 在超过目标值之后就会停。如果迭代器是无限且目标值不存在,才可能真的无限跑下去——所以建议始终配合 .take() 或 .drop() 设定边界。
2. 不支持第二个参数(fromIndex)
Array.prototype.includes(value, fromIndex) 有第二个参数,可以指定从第几位开始查。Iterator 版本没有这个参数,因为迭代器没有「回头」的概念,要跳过前面的值用 .drop(n)。
3. 9月9日才稳定,生产项目建议先 polyfill
虽然 Firefox 已经稳定,Chrome 154 要到9月9日才全体推送。生产项目建议先上 polyfill,别等正式版。
三步下一步
第一步:Chrome 154 上线后立刻用
今天打开 Chrome Canary(154)就能体验,或者直接装 core-js polyfill。打开 DevTools 跑上面的代码感受惰性判断。
第二步:把项目里类似的检查逻辑替换掉
搜一下项目里有没有这种写法:
[...lazyIterator].includes(x)
把前面的 […] 删掉,直接 .includes(x)。记得配上 .take(n) 防止无限序列真的无限跑。
第三步:配合 Iterator.zip / Iterator.helpers 搭管道
Iterator 家族(.filter() / .map() / .take() / .drop() / .zip())加上 .includes(),可以让惰性管道的最后一步检查不再破坏惰性。
一句话总结:Iterator.includes 补上了惰性迭代器「查是否存在」的最后一块短板,让无限序列也能像数组一样用 .includes() 判断,配合 .take() 使用是标准姿势。
评论区
登录后可评论。