在V8引擎处理yield*后跟自定义可迭代对象时,严格遵循ECMAScript规范中的迭代协议,仅通过对象的Symbol.iterator属性进行确定性查找和调用,若该属性缺失或不可调用则立即抛出TypeError,绝不依赖任何鸭子类型或运行时试探性检测。
yield* 后面跟的自定义可迭代对象时,并不会像你想象的那样“看它像不像鸭子就把它当鸭子用”,而是死守 ECMAScript 规范的迭代协议——只认 Symbol.iterator 这个契约入口,一旦找不到或者不可调用,直接甩出一个 TypeError,绝不含糊。

yield* 后面跟自定义可迭代对象时,V8 并不依赖“鸭子类型”做运行时试探性调用,而是严格遵循 ECMAScript 规范的迭代协议,通过符号属性 Symbol.iterator 进行确定性查找与调用。
长期稳定更新的攒劲资源: >>>点此立即查看<<<
当 V8 遇到 yield* 表达式,它会立刻走一遍固定流程:
GetIterator(obj) 这个抽象操作GetIterator 做的第一件事:检查对象上是否有个可调用的 obj[Symbol.iterator] 属性next() 方法)TypeError: obj is not iterable整个过程没有“试探性地调一下 next() 看看它像不像迭代器”这种鸭子类型行为。V8 只认 Symbol.iterator 这个硬性门槛,完全不关心对象“看起来像什么”。
来看一个合法的例子,V8 能正确识别并展开:
const myIterable = {
data: [10, 20, 30],
[Symbol.iterator]() {
let i = 0;
return {
next: () => ({
value: this.data[i],
done: i++ >= this.data.length
})
};
}
};
function* gen() {
yield* myIterable; // V8 查到 Symbol.iterator 并调用
}
如果漏写了 [Symbol.iterator],哪怕对象上确实有 next() 方法,V8 也不会去尝试调用——它根本不会走到那一步。
这不是 V8 的“黑魔法”或者引擎之间的差异,所有主流引擎对 ECMA-262 §7.4.1 GetIterator 的实现都是一致的。所谓的“动态提取”,本质上就是标准协议下的静态符号查找加上函数调用链,不涉及运行时类型推断或方法签名匹配。
真正容易让人混淆的点是:Python 的 __iter__ 是隐式协议,而 JavaScript 的 Symbol.iterator 是显式、唯一、不可替代的入口点。V8 只认这一扇门,不敲门就拒绝服务。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述