JavaScript 中实现递归遍历的 Generator 函数最佳实践
技术百科
碧海醫心
发布时间:2026-01-28
浏览: 次 本文介绍如何用简洁、符合标准的方式实现树形结构的递归迭代器,推荐使用纯函数式 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)
}
}该函数天然满足迭代器协议:调用

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进阶提示:
- 若需异步遍历(如 children 来自 Promise),可改用 async function* 并配合 await yield*;
- 如需提前终止或过滤,可在 yield* 前添加条件判断;
- 若必须封装为类(如需附加方法),应让类仅负责配置,[Symbol.iterator] 内部仍调用纯 generator 函数,而非自行管理状态。
总之,Generator 是 JavaScript 中表达“惰性递归序列”的最自然抽象——拥抱函数式思维,远离手工 next() 和状态管理,代码将更健壮、更易测试、更符合生态惯例。
# ai
# 几个
# 进阶
# 推荐使用
# 而非
# 如需
# 迭代
# 复用
# 路由
# 递归
# 对象
# javascript
# java
# function
# 异步
# this
# node
# bug
# 封装
# 遍历
# for
# 子树
# promise
# 可迭代对象
# symbol
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- c++如何使用std::bind绑定函数参数_c+
- php转exe用什么工具打包快_高效打包软件推荐【
- Win11怎么设置闹钟_Windows 11时钟应
- Win11怎样安装钉钉客户端_Win11安装钉钉教
- c++的STL算法库find怎么用 在容器中查找指
- 用Python构建微服务架构实践_FastAPI与
- php在Linux怎么部署_LNMP环境搭建PHP
- php本地部署后session无法保存_sessi
- Win11怎么开启移动热点_Windows11共享
- C++如何将C风格字符串(char*)转换为std
- 如何开启Windows的远程服务器管理工具(RSA
- 电脑的“网络和共享中心”去哪了_Windows 1
- 如何用正则表达式精确匹配“start”到“end”
- 如何使用Golang指针与结构体结合_修改结构体内
- c# Task.Yield 的作用是什么 它和Ta
- php中::能访问全局变量吗_全局作用域与类作用域
- c++中如何使用auto关键字_c++11类型推导
- 如何使用Golang反射将map转换为struct
- 如何使用Golang模拟请求超时_Golang c
- Win11怎么开启游戏工具栏_Windows11
- Win11怎么查看硬盘型号_Windows 11检
- 短链接怎么自定义还原php_修改解码规则适配需求【
- Win11怎样激活系统密钥_Win11系统密钥激活
- Windows 11无法安全删除U盘提示设备正在使
- Mac怎么进行语音输入_Mac听写功能设置与使用【
- php8.4如何调用com组件_php8.4win
- Win11怎么关闭通知中心_Windows11系统
- Win11怎么设置默认终端应用_Windows11
- 如何在Golang中处理二进制数据_Golang
- Windows蓝屏错误0x00000023怎么修复
- Win11怎么关闭内容自适应亮度_Windows1
- Win11如何设置开机问候语 Win11修改登录界
- 如何使用正则表达式批量替换重复的 *- 模式为固定
- c++如何用AFL++进行模糊测试 c++ Fuz
- c++中explicit(bool)的用法 c++
- Win11怎么关闭定位服务_保护Win11位置隐私
- 如何使用Golang recover捕获panic
- Win11怎么设置应用分屏_Windows11贴靠
- Python与Docker容器化部署实战_镜像构建
- Windows10怎么查看硬件信息_Windows
- Ajax提交表单PHP怎么接收_处理Ajax发送的
- Win11怎么更改鼠标指针_Windows 11自
- Win10怎么卸载金山毒霸_Win10彻底卸载金山
- 如何在Golang中处理模块包路径变化_Golan
- 如何在JavaScript中动态拼接PHP的bas
- Win11输入法选字框不见了怎么办_Win11输入
- Win10电脑怎么设置网络名称_Windows10
- 如何在Golang中定义接口_抽象方法和多态实现
- VSC怎样在Linux运行PHP_Ubuntu系统
- Win11 explorer.exe频繁崩溃_修复

QQ客服