Generator通过yield*委托实现深度优先遍历,避免栈溢出;支持同步树遍历与异步按需加载(如目录递归读取);天然支持早退、分页及外部控制,但需注意内存占用与系统资源限制。

用 Generator 实现深度遍历不确定层级的结构(比如大文件目录树、嵌套 JSON、DOM 树等),核心是把递归逻辑“暂停”和“恢复”起来,避免栈溢出、便于按需消费,还能配合异步操作(如读取大文件时逐层 fetch 或 fs.readFile)。关键不是写个递归函数,而是让每次 yield 只吐一个节点,把控制权交还给调用方。
用 yield* 委托递归,保持扁平调用栈
Generator 本身不解决递归深度问题,但 yield* 能把子生成器的产出“透传”出来,让外层统一消费,避免深层调用堆叠。适合纯同步的树结构遍历:
function* traverseTree(node) {
if (!node) return;
yield node; // 先吐当前节点
if (node.children && Array.isArray(node.children)) {
for (const child of node.children) {
yield* traverseTree(child); // 委托给子生成器,不增加调用栈深度
}
}
}
<p>// 使用示例:一棵简单树
const tree = {
id: 'root',
children: [
{ id: 'a', children: [{ id: 'a1' }] },
{ id: 'b', children: [] }
]
};</p><p>for (const node of traverseTree(tree)) {
console.log(node.id); // root → a → a1 → b
}配合异步操作,边遍历边加载(适合大文件/远程目录)
真实场景中,子节点可能还没加载(比如读取某个目录前得先 fs.readdir),这时不能用同步递归。要用 async function*(异步生成器),每个 await 后 yield 一个结果,真正实现“按需拉取”:
async function* traverseDir(path) {
const entries = await fs.readdir(path, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(path, entry.name);
yield { path: fullPath, type: entry.isDirectory() ? 'dir' : 'file' };
<pre class="brush:php;toolbar:false;">if (entry.isDirectory()) {
yield* traverseDir(fullPath); // 异步委托,内部也会 await
}} }
// 消费时用 for await(注意:必须在 async 函数内) async function logAll() { for await (const item of traverseDir('./src')) { console.log(item.path, item.type); } }
加控制开关,支持提前终止和分页消费
面对超大结构,你可能只想取前 N 项、或遇到特定条件就停。Generator 天然支持早退 —— 调用方 break 即可,生成器内部不会继续执行后续逻辑:
立即学习“Java免费学习笔记(深入)”;
- 无需修改生成器代码,消费端直接
break或用take(n)工具函数 - 如果需要传递信号进生成器(比如“跳过这个分支”),可用
generator.return()或传入控制对象(如{ stop: false, skipChildren: false }) - 常见模式:封装成可中断的迭代器,配合
next(value)接收外部指令
注意内存与性能边界
Generator 不自动释放内存,尤其在闭包持有大对象时。实际使用要留意:
- 避免在生成器函数里缓存整棵树 —— 每次只 hold 当前路径/句柄(如 fs.Dir、ReadStream)
- 对超大文件内容,不要
yield fileContent,而应yield { path, stream: createReadStream(...) },由消费者决定是否读取 - Node.js 中
fs.promises.readdir有默认 limit,但深层嵌套仍可能触发系统级 open 文件数限制,建议加节流或并发控制(如 p-limit)


















