接口返回一串嵌套 JSON,想统计里面所有 id,写了 for in,结果只输出顶层几个键,数组里的对象一个没拿到。

JSON.parse 之后就是普通对象和数组,遍历方式跟普通数据结构没区别,问题出在只走了一层。

for in 的两个毛病

它遍历的是可枚举属性,包括原型链上的。数组用 for in 拿到的是下标字符串,不是元素。稳妥点用 Object.keys 配 forEach,或者 Object.entries 直接拿键值对。

嵌套只能递归

先判空,再判数组,数组走 forEach,对象走 Object.keys,剩下数字和字符串直接跳过。不判类型会死循环,字符串也有 length 和下标。

function walk(node) { if (!node) return; if (Array.isArray(node)) { node.forEach(walk); return; } if (typeof node !== typeof {}) return; Object.keys(node).forEach(function (key) { walk(node[key]); }); }

这段能处理任意深度。要收集某个字段,在 walk 里加一行判断就行,别另写一套遍历。

reviver 也能干这事

JSON.parse 的第二个参数在解析过程中自底向上调用,每个键值对都会过一遍。数据不大时用它统计很方便,缺点是拿不到父级路径。

几个实际问题

对象里有循环引用,递归会栈溢出。接口数据一般不会,本地拼测试数据时容易踩到,加个 WeakSet 记录访问过的对象就行。

数据量大到几万条,递归深度不大但次数多,改成显式栈更稳,浏览器栈深度大概一万层。

JSON 里键名重复,parse 只保留最后一个,这个阶段就丢了,遍历再怎么写也找不回来。

最后提醒一句:遍历只为取数,别在遍历里改原对象,删键会让 forEach 跳过元素,这种 bug 很难复现。