首页 > web前端 > js教程 > 正文

JavaScript递归数组结构转换与父节点数据聚合计算

霞舞
发布: 2025-09-16 10:11:27
原创
872人浏览过

JavaScript递归数组结构转换与父节点数据聚合计算

本文详细阐述如何将具有多层嵌套的JavaScript数组转换为统一的递归树形结构,并着重解决在父节点上聚合其所有子节点数值型数据(如总数和可用量)的挑战。通过分步实现,首先进行结构映射,随后利用后处理机制对父节点数据进行汇总,确保在任意深度层级下都能准确完成数据整合。

1. 问题背景与目标

前端开发中,我们经常需要处理具有复杂嵌套关系的数据结构,例如树形菜单、组织架构或多级分类列表。原始数据可能以扁平化或特定嵌套格式提供,但为了在ui组件(如ant design的tree组件)中展示或进行数据分析,我们需要将其转换为统一的递归树形结构。

本教程的目标是将以下这种包含 group 和 categories、subCategories 的嵌套数组:

const arr = [
  {
    group: { id: "group1", groupname: "groupname1" },
    categories: [
      {
        id: "cat1",
        categoryName: "category1",
        total: 5,
        available: 2,
        subCategories: []
      },
      {
        id: "cat2",
        categoryName: "category2",
        total: 15,
        available: 12,
        subCategories: [
          {
            id: "cat3",
            categoryName: "category3",
            total: 15,
            available: 12,
            subCategories: []
          }
        ]
      }
    ]
  },
  {
    group: { id: "group2", groupname: "groupname2" },
    categories: [
      {
        id: "cat4",
        categoryName: "category4",
        total: 25,
        available: 22,
        subCategories: []
      },
      {
        id: "cat5",
        categoryName: "category5",
        total: 50,
        available: 25,
        subCategories: []
      }
    ]
  }
];
登录后复制

转换为以下统一的树形结构,其中每个节点都包含 key, name, total, available 和 children 属性。特别地,顶层节点(如 group1, group2)的 total 和 available 属性需要聚合其所有子节点的相应值:

[
  {
    "key": "group1",
    "name": "groupname1",
    "total": 35, // 5 + 15 + 15 (cat1.total + cat2.total + cat3.total)
    "available": 26, // 2 + 12 + 12 (cat1.available + cat2.available + cat3.available)
    "children": [
      {
        "key": "cat1",
        "name": "category1",
        "total": 5,
        "available": 2,
        "children": []
      },
      {
        "key": "cat2",
        "name": "category2",
        "total": 30, // 15 + 15 (cat2.total + cat3.total)
        "available": 24, // 12 + 12 (cat2.available + cat3.available)
        "children": [
          {
            "key": "cat3",
            "name": "category3",
            "total": 15,
            "available": 12,
            "children": []
          }
        ]
      }
    ]
  },
  // ... 其他组
]
登录后复制

(注:根据原始问题,group1 的 total 和 available 应该聚合其直接子节点及其孙子节点的所有值。例如,cat2 的 total 应为 15 + 15 = 30,group1 的 total 应为 5 + 30 = 35。原始问题中的期望输出 total: 20, available: 24 可能是一个笔误,或者只考虑了直接子节点。本教程将实现聚合所有子孙节点的功能。)

2. 第一步:构建递归树形结构

首先,我们需要一个递归函数来遍历原始数组,并将其中的 group、category 和 subCategory 对象转换为目标格式的节点。这个函数将负责映射 id 到 key,groupname/categoryName 到 name,并处理 categories/subCategories 到 children 的转换。

立即学习Java免费学习笔记(深入)”;

const formatter = (data) => {
  // 递归处理单个项目,将其转换为目标结构
  const recursiveTree = (item) => {
    if (item.group) { // 处理顶层group项目
      const {
        group: { id, groupname }, // 提取group的id和名称
        categories
      } = item;
      return {
        key: id,
        name: groupname,
        total: 0,      // 初始设置为0,待后续聚合
        available: 0,  // 初始设置为0,待后续聚合
        children: categories?.map(recursiveTree) || [] // 递归处理子分类
      };
    } else { // 处理category或subCategory项目
      const { id, categoryName, total, available, subCategories } = item;
      // 对于category/subCategory,其自身的total和available是已知的
      const children = subCategories?.map(recursiveTree) || [];

      // 如果category/subCategory有子节点,其total和available也需要聚合
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);

      return {
        key: id,
        name: categoryName,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    }
  };

  // 遍历原始数据数组,应用递归转换
  return data.map(recursiveTree);
};
登录后复制

代码解析:

  • formatter 函数接收原始数组 data。
  • recursiveTree 是一个内部递归函数,它根据传入 item 的结构来判断是 group 还是 category/subCategory。
  • 对于 group 类型:
    • 它提取 group.id 和 group.groupname 分别作为 key 和 name。
    • total 和 available 暂时初始化为 0,因为它们最终需要从其所有子孙节点聚合而来。
    • children 属性通过递归调用 categories.map(recursiveTree) 来生成。
  • 对于 category 或 subCategory 类型:
    • 它提取 id、categoryName、total、available。
    • children 属性通过递归调用 subCategories.map(recursiveTree) 来生成。
    • 关键点: 对于 category 或 subCategory 自身,如果它也有子节点,那么它的 total 和 available 也应该聚合自身的值和其所有子孙节点的值。这里在返回之前进行了 reduce 操作,将子节点的 total 和 available 累加到当前节点的初始值上。

3. 第二步:实现顶层父节点数据的聚合计算

上述 recursiveTree 函数已经能够处理 category 和 subCategory 自身的聚合,但对于最顶层的 group 节点,由于其 total 和 available 是从其 categories 聚合而来,而 categories 的聚合又依赖于其 subCategories,这种“自下而上”的计算最好在所有子节点都处理完毕后进行。

即构数智人
即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36
查看详情 即构数智人

一种高效的方法是在 formatter 函数的最后,对已经完成结构转换的顶层节点进行一次后处理。

const formatterWithAggregation = (data) => {
  // 递归处理单个项目,将其转换为目标结构并计算自身及子孙节点的聚合值
  const recursiveTree = (item) => {
    if (item.group) { // 处理顶层group项目
      const {
        group: { id, groupname },
        categories
      } = item;
      // 递归处理所有子分类
      const children = categories?.map(recursiveTree) || [];

      // 对于group节点,其total和available是所有子孙节点的聚合
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, 0);

      return {
        key: id,
        name: groupname,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    } else { // 处理category或subCategory项目
      const { id, categoryName, total, available, subCategories } = item;
      const children = subCategories?.map(recursiveTree) || [];

      // 对于category/subCategory,其自身的total和available是自身值与子孙值的聚合
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);

      return {
        key: id,
        name: categoryName,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    }
  };

  // 直接在map过程中完成所有层级的聚合计算
  return data.map(recursiveTree);
};
登录后复制

优化说明:

在第一次的代码实现中,group 节点的 total 和 available 被初始化为 0,而 category 节点的 total 和 available 则在递归返回时进行了聚合。实际上,我们可以将聚合逻辑统一到 recursiveTree 函数内部,在每次递归返回时,都确保当前节点的 total 和 available 已经包含了其所有子孙节点的聚合值。这样,当 group 节点处理其 children (即 categories) 并得到它们已经聚合好的 total 和 available 后,就可以直接进行求和。

这种“自底向上”的递归聚合方法更为简洁和高效,因为它避免了额外的后处理循环。

4. 完整代码示例

将上述逻辑整合,得到最终的解决方案:

const arr = [
  {
    group: { id: "group1", groupname: "groupname1" },
    categories: [
      {
        id: "cat1",
        categoryName: "category1",
        total: 5,
        available: 2,
        subCategories: []
      },
      {
        id: "cat2",
        categoryName: "category2",
        total: 15,
        available: 12,
        subCategories: [
          {
            id: "cat3",
            categoryName: "category3",
            total: 15,
            available: 12,
            subCategories: []
          }
        ]
      }
    ]
  },
  {
    group: { id: "group2", groupname: "groupname2" },
    categories: [
      {
        id: "cat4",
        categoryName: "category4",
        total: 25,
        available: 22,
        subCategories: []
      },
      {
        id: "cat5",
        categoryName: "category5",
        total: 50,
        available: 25,
        subCategories: []
      }
    ]
  }
];

const formatter = (data) => {
  // 递归处理单个项目,将其转换为目标结构并计算自身及子孙节点的聚合值
  const recursiveTree = (item) => {
    if (item.group) { // 处理顶层group项目
      const {
        group: { id, groupname },
        categories
      } = item;
      // 递归处理所有子分类
      const children = categories?.map(recursiveTree) || [];

      // 对于group节点,其total和available是所有子孙节点的聚合
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, 0);

      return {
        key: id,
        name: groupname,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    } else { // 处理category或subCategory项目
      const { id, categoryName, total, available, subCategories } = item;
      const children = subCategories?.map(recursiveTree) || [];

      // 对于category/subCategory,其自身的total和available是自身值与子孙值的聚合
      // 注意:这里total和available的初始值是当前item自身的total/available
      const aggregatedTotal = children.reduce((sum, child) => sum + child.total, total || 0);
      const aggregatedAvailable = children.reduce((sum, child) => sum + child.available, available || 0);

      return {
        key: id,
        name: categoryName,
        total: aggregatedTotal,
        available: aggregatedAvailable,
        children: children
      };
    }
  };

  // 直接在map过程中完成所有层级的聚合计算
  return data.map(recursiveTree);
};

const result = formatter(arr);
console.log(JSON.stringify(result, null, 2));
登录后复制

5. 注意事项与扩展

  1. N层深度支持: 这种递归方法天然支持任意深度的嵌套,无需额外修改。
  2. 默认值处理: 在聚合计算中,total || 0 和 available || 0 的写法确保了即使原始数据中缺少 total 或 available 字段,也能将其视为 0 进行计算,避免了 NaN 的出现。
  3. 性能考量: 对于非常庞大和深层的数据结构,递归可能会导致栈溢出。在JavaScript中,通常浏览器对递归深度有限制(约几千层)。如果遇到此类问题,可以考虑使用迭代方式(如队列或栈)模拟递归,或者进行尾递归优化(如果环境支持)。但对于大多数常见应用场景,此递归方案足够高效。
  4. 聚合逻辑扩展: 如果需要聚合的不仅仅是 total 和 available,或者需要不同的聚合方式(如平均值、最大值),只需在 reduce 回调函数中调整相应的逻辑即可。
  5. 错误处理: 在实际应用中,可能需要增加对输入数据格式的校验,例如检查 group、categories、subCategories 等属性是否存在,以增强代码的健壮性。

总结

本教程通过一个两阶段(但最终优化为一步到位)的递归处理方法,成功地将复杂的嵌套数组结构转换为统一的树形结构,并实现了父节点对其所有子孙节点数值型数据的聚合计算。核心在于利用递归的“自底向上”特性,在每次递归调用返回时,确保当前节点的聚合值已经计算完毕,从而使得上层节点可以直接使用这些聚合值进行进一步的汇总。这种模式在处理各种树形数据结构转换和数据汇总的场景中具有广泛的应用价值。

以上就是JavaScript递归数组结构转换与父节点数据聚合计算的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号