
本文介绍如何用简洁、符合标准的方式实现树形结构的递归迭代器,推荐使用纯函数式 generator(而非类封装),避免手动维护状态,同时自然支持 `for...of` 和解构语法。
在 JavaScript 中,要对嵌套结构(如带 children 的路由树)进行深度优先遍历并暴露为可迭代对象,最清晰、最符合语言惯例的做法是直接定义一个递归 generator 函数,而非构造一个需手动管理内部状态的类。你当前的 IteratorClass 虽然能运行,但存在几个关键设计隐患:
- ❌ 违反迭代器协议最小化原则:[Symbol.iterator] 方法本应返回一个迭代器对象(即具有 next() 方法的对象),而你的实现直接返回了 generator 对象——这虽因 generator 本身实现了迭代器协议而“碰巧有效”,但掩盖了语义混淆:IteratorClass 实例并非迭代器,而是可生成迭代器的可迭代对象;真正的迭代器由 *[Symbol.iterator] 内部创建并返回。
- ❌ 状态耦合与不可复用性:this._routes 和 this._depth 将数据与遍历深度绑定在实例上,导致同一实例无法被多次迭代(generator 一旦耗尽即不可重用),且无法灵活传入不同初始深度或定制逻辑。
- ❌ 副作用污染数据:route.depth = this._depth 直接修改原始 route 对象,破坏了数据的不可变性,易引发隐蔽 bug。
✅ 推荐方案:使用无状态、纯函数式的递归 generator:
function* recurIter(data, depth = 1) {
for (const node of data) {
// 深度信息通过解构注入新对象,不修改原数据
const { children, ...rest } = { ...node, depth };
if (Array.isArray(children) && children.length > 0) {
yield* recurIter(children, depth + 1); // 递归进入子树
}
yield rest; // 输出当前节点(含 depth)
}
}该函数天然满足迭代器协议:调用 recurIter(data) 即返回一个 generator 对象,该对象既是可迭代的(支持 for...of),也是迭代器(自带 next())。使用时无需实例化、无副作用、可无限复用:
const data = [
{
key: 1,
path: '/users',
name: 'users',
children: [
{ key: 2, path: '/users/roles', name: 'roles' },
{ key: 3, path: '/users/permissions', name: 'permissions' }
]
},
{
key: 4,
path: '/projects',
name: 'projects',
children: [
{
key: 5,
path: '/projects/milestones',
name: 'milestones',
children: [
{ key: 6, path: '/projects/milestones/tasks', name: 'tasks' }
]
}
]
}
];
// 安全、清晰、可解构
for (const { name, depth, path } of recurIter(data)) {
console.log(`ROUTE: "${name}" | PATH: "${path}" | DEPTH: ${depth}`);
}
// 输出:
// ROUTE: "users" | PATH: "/users" | DEPTH: 1
// ROUTE: "roles" | PATH: "/users/roles" | DEPTH: 2
// ROUTE: "permissions" | PATH: "/users/permissions" | DEPTH: 2
// ROUTE: "projects" | PATH: "/projects" | DEPTH: 1
// ROUTE: "milestones" | PATH: "/projects/milestones" | DEPTH: 2
// ROUTE: "tasks" | PATH: "/projects/milestones/tasks" | DEPTH: 3进阶提示:
立即学习“Java免费学习笔记(深入)”;
- 若需异步遍历(如 children 来自 Promise),可改用 async function* 并配合 await yield*;
- 如需提前终止或过滤,可在 yield* 前添加条件判断;
- 若必须封装为类(如需附加方法),应让类仅负责配置,[Symbol.iterator] 内部仍调用纯 generator 函数,而非自行管理状态。
总之,Generator 是 JavaScript 中表达“惰性递归序列”的最自然抽象——拥抱函数式思维,远离手工 next() 和状态管理,代码将更健壮、更易测试、更符合生态惯例。










