<p>V8 严格遵循 ECMAScript 迭代协议,yield* 仅通过 Symbol.iterator 确定性查找并调用,不进行鸭子类型试探;若对象无该属性或不可调用,立即抛出 TypeError。</p>
yield* 后面跟自定义可迭代对象时,V8 并不依赖“鸭子类型”做运行时试探性调用,而是严格遵循 ECMAScript 规范的迭代协议,通过符号属性 Symbol.iterator 进行**确定性查找与调用**。
V8 在遇到 yield* 表达式时,会立即执行以下步骤:
GetIterator(obj) 抽象操作GetIterator 首先检查对象是否具有可调用的 obj[Symbol.iterator] 属性next() 方法)TypeError: obj is not iterable
这个过程没有“试探性调用 next()”“看它像不像迭代器”等鸭子类型行为。它只认 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 }) }; }};<p>function<em> gen() {yield</em> myIterable; // ✅ V8 查到 Symbol.iterator 并调用}
如果漏掉 [Symbol.iterator],哪怕对象有 next() 方法,V8 也不会尝试调用 —— 它根本不会走到那一步。
这不是 V8 的“黑魔法”或引擎差异,而是所有主流引擎对 ECMA-262 §7.4.1 GetIterator 的标准实现。所谓“动态提取”,实为标准协议下的静态符号查找 + 函数调用链,不涉及运行时类型推断或方法签名匹配。
真正容易混淆的是:Python 的 __iter__ 是隐式协议,而 JS 的 Symbol.iterator 是显式、唯一、不可替代的入口点。V8 只认这一个门,不敲门就拒绝服务。