不能直接用 for...of 遍历嵌套对象,因为普通对象未实现[Symbol.iterator],强行遍历会报TypeError;应通过递归生成器配合yield*和WeakSet处理循环引用,实现懒求值的深度遍历。

为什么不能直接用 for...of 遍历嵌套对象
因为 for...of 只能消费实现了 [Symbol.iterator] 的对象,而普通对象默认不提供迭代器,更不会自动递归进入 children、items 或其他嵌套字段。强行遍历时会报 TypeError: obj is not iterable。
常见错误是写个递归函数返回数组,再用 for...of 遍历——这等于放弃迭代器的懒求值优势,还多占内存。
如何让自定义结构支持 for...of + 深度遍历
核心是实现一个递归生成器(generator function),在内部用 yield* 委托子迭代器,而不是拼接数组。
- 必须用
function*声明,返回Generator对象 - 对每个节点:先
yield当前值,再对每个子节点调用自身并yield* - 子节点提取逻辑要可配置,比如取
node.children、node.items或由传入的getChildren函数决定 - 需处理循环引用,否则栈溢出;可用
WeakSet缓存已访问对象
示例:
class Tree {
constructor(value, children = []) {
this.value = value;
this.children = children;
}
<p><em>[Symbol.iterator]() {
yield</em> this.#traverse(this);
}</p><h1>traverse(node, seen = new WeakSet()) {</h1><pre class="brush:php;toolbar:false;">if (seen.has(node)) return;
seen.add(node);
yield node.value;
for (const child of node.children) {
yield* this.#traverse(child, seen);
}
} }
之后就能直接 for (const val of new Tree(1, [new Tree(2)])) { ... }。
迭代器参数化:适配不同嵌套字段名
硬编码 children 无法复用。应把子节点提取逻辑抽成参数,在构造时或遍历时传入。
- 推荐在生成器方法上加参数,如
depthFirst({ getChildren: n => n.nodes || n.items }) - 避免在
[Symbol.iterator]中传参,因为for...of不支持传参调用 - 若必须用
for...of,可提供多个迭代器方法:[Symbol.iterator]用默认字段,depthFirst(opts)供手动调用 - 注意:每次调用生成器都新建
WeakSet,否则跨多次遍历会漏判循环引用
性能与边界要注意什么
递归生成器看似简洁,但深度极大时仍可能触发 V8 的调用栈限制(约 10000 层),尤其在旧版 Node.js 中。
- 浏览器环境一般没问题;Node.js 16+ 默认栈更大,但极端情况仍需改写为显式栈(while + Array)
-
yield*有微小开销,若只遍历一次且数据量小,直接递归返回数组反而更快 - 不要在生成器里做重操作(如 DOM 查询、IO),它可能被中途
break或return,导致逻辑不完整 - 调试时别用
console.log([...iter])查看全部结果——这会强制执行整个生成器,掩盖懒求值特性
真正要用好它,得清楚自己是在优化内存(流式处理大结构),还是只是想写得“看起来高级”。后者往往不如老实写个递归函数。










