导读:本期聚焦于小伙伴创作的《如何用JavaScript递归遍历并统计嵌套对象和数组中的元素数量》,敬请观看详情。在处理后端返回的复杂JSON时,常常需要弄清数据里到底嵌了多少层、有多少字段和数组项。直接靠手写循环很难覆盖所有情况,因为对象里可能嵌套对象,数组里又套着对象。通过递归函数可以统一处理这两种结构:遇到对象就枚举自身属性并继续向下,遇到数组就遍历每一项再向下。统计时建议区分键名数量与数组长度,避免把对象属性误算成数组元素。下面给出可复用的计数函数与示例,说明如何同时得到对象层数、属性总数与数组项总数,并提醒注意循环引用导致的栈溢出问题。

在JavaScript开发中,我们经常会拿到结构不固定的深层数据,比如接口返回的配置文件、树形菜单或者多维报表。如果想知道这份数据里一共包含多少个对象属性、多少个数组元素,以及嵌套最深有多少层,用普通的for循环很难写干净。递归是最直接的思路:让函数自己调用自己,每碰到对象或数组就进一层。

如何用JavaScript递归遍历并统计嵌套对象和数组中的元素数量

递归遍历的基本思路

递归遍历的核心是把“当前节点”当成统一入口。如果当前值是普通对象(不含null,且不是数组),我们就用Object.keys拿到它自己的属性名,对每个属性值再调用同一函数;如果当前值是数组,就按索引取出每一项继续调用。这样无论数据嵌套多少层,处理逻辑只有一套。

为了不把数组和对象搞混,判断类型时推荐用Array.isArray和typeof结合。typeof null会返回object,所以要单独排除。递归函数一般接收两个参数:当前处理的值,以及记录状态的容器对象。容器里可以放属性计数、数组项计数和当前深度。

function traverse(value, stats, depth) {
  depth = depth || 1;
  if (stats.maxDepth < depth) {
    stats.maxDepth = depth;
  }
  if (Array.isArray(value)) {
    stats.arrayItems += value.length;
    for (var i = 0; i < value.length; i++) {
      traverse(value[i], stats, depth + 1);
    }
  } else if (value !== null && typeof value === 'object') {
    var keys = Object.keys(value);
    stats.objectKeys += keys.length;
    for (var k = 0; k < keys.length; k++) {
      traverse(value[keys[k]], stats, depth + 1);
    }
  }
}

完整的计数示例

下面把上面的函数包成一个易用的入口,初始化统计对象后返回结果。这样调用方只需要传入根数据,就能拿到属性总数、数组项总数和最大深度。注意我们初始化maxDepth为0,当根本身就是对象或数组时,第一次进入就会更新它。

在示例数据里,我们故意放了一个对象嵌套数组、数组又嵌套对象的结构,用来验证递归是否真的走透了每一层。运行后可以看到,对象键和数组项被分别累加,不会互相干扰。

function countNested(input) {
  var stats = { objectKeys: 0, arrayItems: 0, maxDepth: 0 };
  traverse(input, stats, 1);
  return stats;
}

var data = {
  name: 'root',
  children: [
    { id: 1, tags: ['a', 'b'] },
    { id: 2, info: { age: 3, score: [90, 80] } }
  ],
  meta: { valid: true }
};

var result = countNested(data);
console.log(result);
// 输出: { objectKeys: 8, arrayItems: 4, maxDepth: 4 }

处理循环引用与性能注意点

真实数据里偶尔会出现循环引用,比如对象A的属性指向B,B又指回A。如果递归函数不防备这点,就会无限调用直到调用栈溢出。解决办法是额外传一个WeakSet,记录已经访问过的对象,进入前先判断,访问过就跳过。

另外,当数据非常庞大且层级极深时,递归会占用不少调用栈空间。若浏览器报超限错误,可以考虑把递归改成基于栈或队列的迭代写法,用while循环手动维护待处理列表,思路一样但更省栈。不过对绝大多数业务数据,上面的递归写法已经够用且更好读。

function traverseSafe(value, stats, depth, seen) {
  seen = seen || new WeakSet();
  if (value !== null && typeof value === 'object') {
    if (seen.has(value)) return;
    seen.add(value);
  }
  depth = depth || 1;
  if (stats.maxDepth < depth) stats.maxDepth = depth;
  if (Array.isArray(value)) {
    stats.arrayItems += value.length;
    value.forEach(function(item) {
      traverseSafe(item, stats, depth + 1, seen);
    });
  } else if (value !== null && typeof value === 'object') {
    var keys = Object.keys(value);
    stats.objectKeys += keys.length;
    keys.forEach(function(key) {
      traverseSafe(value[key], stats, depth + 1, seen);
    });
  }
}

使用场景小结

这种递归计数方式在写调试工具、数据校验中间件或者可视化树形结构时都很有用。你可以轻松知道接口返回是否比预期胖了很多,或者某个字段是不是悄悄多嵌套了两层。只要记住区分对象键和数组项,并处理好循环引用,代码就能稳定跑在生产环境。

如果后续还要对每种键名做分类统计,只需把stats从数字改成以键名为维度的对象即可,递归框架完全不用动。这也是递归相比手写多层循环最大的优势:结构变化不用改流程。

JavaScript递归遍历嵌套对象修改时间:2026-08-02 05:09:11

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。