generator 配合 yield 实现的是递归式生成器、非递归式执行的扁平化迭代,利用暂停/恢复机制避免调用栈溢出,空间复杂度降为 o(d);yield 是委托语法,实现链式遍历,无需手动维护栈。

Generator 配合 yield* 实现的“非递归扁平化迭代”,这个说法需要先厘清一个关键点:它本质仍是递归逻辑,但执行方式是非阻塞、按需迭代的。所谓“非递归”,指的是不依赖函数调用栈逐层展开(避免栈溢出),而是利用 Generator 的暂停/恢复机制 + 递归调用自身生成器,实现内存友好、流式产出的扁平化——准确说是递归式 Generator,非递归式执行。
核心优势在于:不一次性构建完整结果数组,每次 next() 只计算并返回下一个值,空间复杂度从 O(n) 降至 O(d),d 为最大嵌套深度。
为什么 yield* 是关键
yield* 不是普通 yield,它是“委托”语法:把当前生成器的执行权,临时交给另一个可迭代对象(比如另一个 generator 函数)。当被委托的 generator 执行完毕,控制权自动交还。这使得嵌套结构的遍历天然形成链式调用,无需手动维护栈或队列。
标准实现(支持任意深度)
以下是一个简洁、健壮、符合现代 JS 规范的写法:
- 用
function*声明生成器函数 - 遍历输入数组,对每个元素判断是否为数组
- 若是数组,用
yield* flat(item)委托给自身处理 - 若不是,直接
yield item
示例代码:
function* flat(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flat(item); // ← 关键:递归委托,不压栈,只挂起当前上下文
} else {
yield item;
}
}
}
<p>// 使用方式:可遍历、可转数组、可配合 for...of 或扩展运算符
const nested = [1, [2, [3, 4]], [5, [6, [7]]]];
const result = [...flat(nested)]; // [1, 2, 3, 4, 5, 6, 7]
</p>
如何避免常见陷阱
-
不要混用 return 和 yield*:Generator 中 return 会立即终止,且其值不会被
yield*捕获;确保所有分支都通过 yield 或 yield* 输出 -
注意稀疏数组和空位:
for...of会跳过空位(如[1, , 3]),与flat()行为一致;若需保留索引位置,改用for (let i = 0; i -
类型安全判断优先用
Array.isArray(),而非typeof item === 'object',后者会误判 null、Date、Object 等
进阶:带路径信息的扁平化(适用于树形配置)
如果原始数据是带 key 的树形结构(如菜单、分类、权限节点),你可能需要同时输出值和路径。这时可在递归调用时传递当前路径:
function* flatWithPath(obj, path = []) {
if (Array.isArray(obj)) {
for (let i = 0; i <p>这样每项产出都是 <code>{ value, path }</code>,便于后续映射或调试。</p>










