yield* 是迭代委托机制,自动调用右侧可迭代对象的[symbol.iterator]()并逐个yield值,不增加调用栈深度;适用于树遍历和嵌套数组扁平化,以生成器链式消费替代递归调用。

可以利用 yield* 将嵌套结构的遍历“委托”给子迭代器,配合生成器函数实现逻辑上扁平、执行上非递归的遍历——它不靠函数调用栈展开嵌套,而是靠迭代器协议逐层移交控制权。
理解 yield* 的委托本质
yield* 不是语法糖,而是一种迭代委托机制:它会自动调用右侧可迭代对象的 [Symbol.iterator](),并把该迭代器产生的每个值依次 yield 出来。整个过程不新建调用帧,不增加 JS 调用栈深度。
- 右侧可以是数组、Set、Map、生成器函数调用结果,或任何实现了迭代协议的对象
- 遇到
yield* gen()时,当前生成器暂停,控制权交给gen()返回的迭代器;等它耗尽后,才继续执行后续语句 - 这天然适合“展开一层再交棒”的嵌套遍历场景
对树形结构做非递归遍历(生成器版)
以多叉树为例,节点有 value 和 children(数组):
function* traverseTree(node) {
yield node.value;
if (Array.isArray(node.children)) {
for (const child of node.children) {
yield* traverseTree(child); // 委托给子树迭代器,不递归调用
}
}
}
注意:这里 traverseTree(child) 返回的是一个生成器对象(迭代器),yield* 消费它——函数本身仍被调用,但**控制流不堆叠**。真正避免栈溢出的是:每次只持有一个活跃的生成器上下文,旧上下文在委托完成后即被释放。
对任意嵌套数组做扁平化遍历
更典型的场景是深度不确定的嵌套数组:
function* flatten(items) {
for (const item of items) {
if (Array.isArray(item)) {
yield* flatten(item); // 把子数组的遍历完全委托出去
} else {
yield item;
}
}
}
<p>// 使用
const nested = [1, [2, 3], [4, [5, 6]], 7];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5, 6, 7]
</p>
关键点:
- 不需要手写栈模拟递归
- 不需要 while + push/pop 管理待处理片段
- 所有“深入下一层”的动作都由 yield* 自动完成,语义清晰且内存友好
与手动迭代器/栈方案的本质区别
非递归 ≠ 一定用显式栈。常见误区是认为“不用递归就必须自己维护 stack 数组”。其实生成器+yield* 提供了第三条路:
- 传统递归:调用栈深度 = 嵌套深度 → 深度大时爆栈
- 显式栈(while + Array):用堆内存换栈空间,逻辑复杂,易出错
-
yield*委托:引擎内部管理多个暂停/恢复的生成器上下文,每个只占少量内存,无栈溢出风险,代码接近递归直觉
它不是“消除递归调用”,而是把递归调用转化为**迭代器链式消费**,由 JavaScript 引擎在底层高效调度。










