JavaScript中JSON树形结构需手动实现扁平化迭代器才能用for-of遍历,因普通对象无Symbol.iterator;通过生成器函数treeEntries递归提取叶子节点及路径,再封装为类支持直接for-of消费。

JavaScript中用Iterator接口配合for-of遍历复杂JSON树形结构,核心是**手动实现一个可迭代的扁平化遍历器**,而不是直接对原始JSON对象使用for-of(因为普通对象默认不可迭代)。关键在于:把嵌套结构“摊开”成一串有序的键值对或路径值对,并让这个摊开过程可被for-of消费。
1. 为什么JSON对象不能直接for-of?
普通JavaScript对象(包括解析后的JSON)没有内置[Symbol.iterator]方法,所以for-of会报错:TypeError: is not iterable。必须显式提供迭代逻辑——也就是实现一个返回迭代器对象的Symbol.iterator方法。
2. 手写TreeIterator:支持深度优先扁平化
针对树形配置(如菜单、权限规则、表单字段嵌套),常见需求是按DFS顺序提取所有叶子节点或带路径的键值对。下面是一个通用的扁平化迭代器示例:
说明:它递归遍历任意深度的嵌套对象/数组,把每个非对象/非数组的值(即“叶子”)作为一项产出,同时可选地附带访问路径(如["menu", "sub", "0", "label"])。
立即学习“Java免费学习笔记(深入)”;
- 用生成器函数
function*最简洁,自动实现next()和Symbol.iterator - 路径用数组表示,便于后续做key拼接(如
menu.sub.0.label)或定位 - 跳过
null、undefined、函数等非数据类型
示例代码:
function* treeEntries(obj, path = []) {
if (obj === null || obj === undefined || typeof obj === 'function') {
return;
}
<p>if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
yield<em> treeEntries(obj[i], [...path, String(i)]);
}
} else if (typeof obj === 'object') {
for (const [key, value] of Object.entries(obj)) {
yield</em> treeEntries(value, [...path, key]);
}
} else {
// 叶子节点:原始值(string/number/boolean等)
yield { path, value: obj };
}
}</p><p>// 使用方式
const config = {
app: { name: "Admin", version: "2.1" },
menu: [
{ id: "home", label: "首页" },
{ id: "user", label: "用户", children: [{ id: "list", label: "列表" }] }
]
};</p><p>for (const { path, value } of treeEntries(config)) {
console.log(path.join('.'), '=', value);
}
// 输出:
// app.name = Admin
// app.version = 2.1
// menu.0.id = home
// menu.0.label = 首页
// menu.1.id = user
// menu.1.label = 用户
// menu.1.children.0.id = list
// menu.1.children.0.label = 列表
3. 封装成可直接for-of的对象(更贴近“接口”语义)
如果希望像for (item of new FlatConfigIterator(config))这样调用,可以包装成类:
class FlatConfigIterator {
constructor(config) {
this.config = config;
}
*[Symbol.iterator]() {
yield* treeEntries(this.config);
}
}
<p>// 然后就能这样用:
for (const { path, value } of new FlatConfigIterator(config)) {
// 处理每一项
}
这种写法明确表达了“这是一个可迭代的扁平化视图”,也方便扩展(比如加过滤条件、只取字符串值、跳过特定字段名等)。
4. 实际读取时的常见处理技巧
真实配置文件往往需要进一步加工,比如:
-
只读取特定类型值:在
yield前加判断,如if (typeof obj === 'string' && obj.trim()) -
跳过黑名单字段:如
if (key === 'meta' || key === '$schema') continue; -
路径转为点分字符串:用
path.join('.') + ': ' + value生成配置摘要 -
收集所有i18n文案:当
path[path.length-1] === 'text'或key === 'label'时单独提取
不复杂但容易忽略:记得在递归入口处检查循环引用(seen.has(obj)),否则深层嵌套或意外环状结构会栈溢出。简单项目可省略,生产环境建议加上。


















