树形结构扁平遍历可通过部署[Symbol.iterator]实现惰性迭代:生成器方式用yield*递归委托,手动方式用栈模拟,均支持for...of且不占额外内存。

用迭代协议实现树形结构的扁平遍历,核心是让树对象自身支持 for...of 语法——只需在树节点类上部署 [Symbol.iterator] 方法,返回一个遵循迭代器协议的对象(含 next()),就能把“遍历树”变成“遍历普通可迭代值”,无需提前展开、不占额外内存。
让树节点具备可迭代能力
每个树节点(如包含 value 和 children 的对象)都可以实现自己的迭代器。关键不是一次性收集所有值,而是按需产出:
-
定义
[Symbol.iterator]():返回一个生成器函数或手动实现的迭代器对象 -
优先使用生成器:天然支持递归委托(
yield*),代码简洁且自动管理执行状态 -
避免深拷贝或中间数组:每次
next()只计算下一个有效节点,适合大型或动态树
用生成器 + yield* 实现惰性前序遍历
这是最自然、最符合迭代协议精神的做法。假设节点结构为 { value, children: Node[] }:
Node.prototype[Symbol.iterator] = function* () {
yield this.value;
if (this.children) {
for (const child of this.children) {
yield* child; // 委托给子节点的迭代器,自动递归展开
}
}
};
这样写完后,任意节点 root 就可以直接用于 for (const v of root) { ... } 或 [...root],结果就是前序扁平序列。
手动实现迭代器(无生成器环境)
若需兼容不支持生成器的环境,可手动维护遍历栈:
- 初始化时将根节点压入栈
-
next()中弹出栈顶,产出其value,再将其children逆序压入栈(保证左→右顺序) - 栈空即
done: true
这种方式完全避开递归调用栈,也不依赖 yield,适合超深层树或严格控制执行流的场景。
配合扁平化使用的真实场景
比如处理带路径信息的配置树,你可能需要同时产出值和层级路径:
- 在生成器中传入当前路径(如
['root', 'child1']),每进一层就拼接新键名 - 产出对象
{ value, path }而非裸值,便于后续映射或筛选 - 仍保持惰性:路径只在
next()调用时实时构建,不预计算整棵树的路径表











