0

0

深度解析:JavaScript中如何按层级汇总嵌套数据结构中的金额

霞舞

霞舞

发布时间:2025-10-08 10:17:01

|

559人浏览过

|

来源于php中文网

原创

深度解析:javascript中如何按层级汇总嵌套数据结构中的金额

本文详细阐述了如何在多层嵌套的树形数据结构中,按层级精确计算并汇总每个层级的存款总额。通过递归遍历和层级聚合的策略,提供了一种高效且结构清晰的JavaScript解决方案,适用于处理如推荐系统、组织架构等场景中的分层数据,确保能准确获取每个层级的独立总和,而非扁平化的所有存款列表。

问题背景与数据结构

在许多业务场景中,我们经常会遇到具有层级关系的数据,例如公司的组织架构、用户的推荐系统(多级下线)、文件目录等。这些数据通常以嵌套的JSON对象或数组形式表示。本教程将以一个典型的用户层级存款数据为例,演示如何计算每个层级的存款总额。

假设我们有一个表示用户及其下线的层级结构,每个用户节点包含一个deposit字段表示其存款金额,以及一个可选的children数组,表示其直接下线。层级深度可能不固定,但通常会有最大限制(例如,最多5层)。我们的目标是得到一个数组,其中每个元素代表对应层级的存款总和。

以下是示例数据结构片段:

[
    {
        "id": "ddf86d60-a607-4a4e-a7f9-d96013ee7070",
        "name": "Rick Rich",
        "deposit": 100,
        "children": [
            {
                "id": "25de2e98-eb2d-41f4-b225-3069f942b284",
                "name": "Rick Rich",
                "deposit": 100,
                "children": [
                    {
                        "id": "376b202e-d44f-4402-9560-8498c855d05e",
                        "name": "Rick Rich",
                        "deposit": 100,
                        "children": [
                            { "deposit": 100 },
                            { "deposit": 100 },
                            { "deposit": 100 }
                        ]
                    },
                    { "deposit": 100, "children": [] },
                    { "deposit": 100, "children": [] }
                ]
            },
            { "deposit": 100, "children": [] },
            { "deposit": 100, "children": [] }
        ]
    },
    { "deposit": 100, "children": [] },
    { "deposit": 0, "children": [] }
]

期望的输出结果是一个数组,例如 [300, 300, 300, 300],表示第一层总和、第二层总和、第三层总和等。

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

错误的尝试及原因分析

初学者在处理此类问题时,可能会尝试使用简单的迭代或递归,但容易陷入将所有存款扁平化收集的误区。例如,以下代码会收集所有节点的存款,而不是按层级汇总:

NanoAI
NanoAI

AI绘画与智能图片编辑平台

下载
// 假设 Children 类型已定义
// const [userDeposit, setUserDeposit] = useState([]); // 在React等框架中可能这样使用

const iterateOfChildrenDepositWrong = (
    children: any[], // 简化类型为any[]
    result: number[] = [],
): void => {
    children.forEach((node: any) => {
        result.push(node.deposit); // 这里直接将存款添加到结果数组
        if (node.children && node.children.length > 0) {
            iterateOfChildrenDepositWrong(node.children, result);
        }
    });
    // setUserDeposit(result); // 在实际应用中可能会更新状态
};

// 调用示例
// let allDeposits: number[] = [];
// iterateOfChildrenDepositWrong(initialData, allDeposits);
// console.log(allDeposits); // 输出将是所有存款的扁平列表,如 [100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 0]

上述代码的问题在于,它在遍历每个节点时,直接将其deposit值添加到同一个result数组中。当递归调用处理子节点时,子节点的存款也会被添加到这个result数组中,导致最终得到的是所有层级存款的混合列表,无法区分每个层级的总和。要实现按层级汇总,我们需要在每次处理一个层级时,计算该层级的总和,并将其作为一个独立的元素添加到结果数组中。

正确的层级汇总方法:递归与广度优先思想

为了按层级汇总存款,我们需要一种方法来在处理完一个层级的所有节点后,再进入下一个层级。这本质上是一种广度优先遍历(BFS)的思想,但通过递归也可以优雅地实现。核心思路是:

  1. 初始化结果数组:用于存储每个层级的存款总和。
  2. 处理当前层级:遍历当前层级的所有节点,计算它们的存款总和。
  3. 收集下一层级节点:在处理当前层级的同时,将所有当前节点的子节点收集起来,形成“下一层级”的节点列表。
  4. 递归调用:如果下一层级存在节点,则以这些节点作为新的“当前层级”进行递归调用。

示例代码

以下是实现按层级汇总存款的JavaScript函数:

// 简化后的示例数据,只包含deposit和children字段
let hierarchicalData = [
    {
        "deposit": 100,
        "children": [
            {
                "deposit": 100,
                "children": [
                    {
                        "deposit": 100,
                        "children": [
                            { "deposit": 100 },
                            { "deposit": 100 },
                            { "deposit": 100 }
                        ]
                    },
                    { "deposit": 100, "children": [] },
                    { "deposit": 100, "children": [] }
                ]
            },
            { "deposit": 100, "children": [] },
            { "deposit": 100, "children": [] }
        ]
    },
    { "deposit": 100, "children": [] },
    { "deposit": 0, "children": [] }
];

let levelWiseDeposits: number[] = []; // 用于存储最终结果的数组

/**
 * 递归函数,用于按层级汇总存款
 * @param children 当前层级的节点数组
 * @param result 存储每个层级总和的数组(通过引用传递)
 */
function iterateOfChildrenDeposit(children: any[], result: number[]): void {
    let currentLevelSum = 0; // 当前层级的存款总和
    let nextLevelChildren: any[] = []; // 存储下一层级的所有子节点

    // 遍历当前层级的所有节点
    children.forEach((node: any) => {
        currentLevelSum += node.deposit; // 累加当前节点的存款
        // 如果当前节点有子节点,则将其添加到下一层级列表中
        if (node.children && node.children.length > 0) {
            nextLevelChildren = nextLevelChildren.concat(node.children);
        }
    });

    // 将当前层级的总和添加到结果数组中
    result.push(currentLevelSum);

    // 如果下一层级存在节点,则递归调用自身处理下一层级
    if (nextLevelChildren.length > 0) {
        iterateOfChildrenDeposit(nextLevelChildren, result);
    }
    // 递归终止条件:当nextLevelChildren为空时,表示没有更多层级
}

// 调用函数开始计算
iterateOfChildrenDeposit(hierarchicalData, levelWiseDeposits);

console.log('按层级汇总的存款:', levelWiseDeposits);
// 预期输出: [300, 300, 300, 300]

代码解析

  1. levelWiseDeposits: number[] = []: 这是一个在函数外部声明的数组,用于存储最终的按层级汇总结果。它作为参数传递给递归函数,并通过引用在每次递归调用中被修改。
  2. iterateOfChildrenDeposit(children: any[], result: number[]): void:
    • children: 代表当前正在处理的层级的所有节点。
    • result: 引用外部的levelWiseDeposits数组,用于累积每个层级的总和。
  3. currentLevelSum = 0;: 在每次函数调用(即处理一个新的层级)开始时,都会初始化一个变量来累加当前层级的存款。
  4. nextLevelChildren: any[] = [];: 同样,每次处理新层级时,都会初始化一个空数组来收集当前层级所有节点的子节点,这些子节点将构成下一个层级。
  5. children.forEach((node: any) => { ... });: 遍历children数组(当前层级的所有节点)。
    • currentLevelSum += node.deposit;: 将当前节点的存款加到currentLevelSum中。
    • if (node.children && node.children.length > 0) { nextLevelChildren = nextLevelChildren.concat(node.children); }: 检查当前节点是否有子节点。如果有,则将这些子节点添加到nextLevelChildren数组中。concat方法用于将多个子节点数组合并成一个。
  6. result.push(currentLevelSum);: 在遍历完当前层级的所有节点并计算出currentLevelSum后,将其添加到result数组中。这确保了每个元素都是一个层级的总和。
  7. if (nextLevelChildren.length > 0) { iterateOfChildrenDeposit(nextLevelChildren, result); }: 这是一个关键的递归步骤。如果nextLevelChildren数组不为空(即存在下一层级),则以nextLevelChildren作为新的children参数,递归调用iterateOfChildrenDeposit函数,继续处理下一层级。
  8. 递归终止条件: 当nextLevelChildren.length为0时,表示当前层级的所有节点都没有子节点,即没有下一层级了,递归调用停止。

注意事项与扩展

  • 数据深度限制: 原始问题中提到最大5层,但此递归方法可以处理任意深度的层级结构,只要不超出JavaScript的递归深度限制(通常很高,对于5层来说完全不是问题)。
  • 空节点或无存款: 如果某个节点没有deposit字段或其值为null/undefined,在实际应用中需要进行额外的检查或默认值处理,以避免NaN。本示例假设deposit始终为有效数字。
  • 性能考量: 对于非常庞大和深层的数据结构,递归可能会导致溢出。在这种极端情况下,可以考虑使用基于队列的迭代式广度优先搜索(BFS)算法来避免深层递归。然而,对于大多数实际应用(如5层),递归是简洁高效的选择。
  • 通用性: 此模式不仅适用于计算存款总额,还可以轻松修改以计算其他数值属性的总和、计数节点数量、收集特定属性等,只需调整currentLevelSum的计算逻辑和result数组的存储内容即可。
  • 框架集成: 在React等前端框架中,可以将levelWiseDeposits作为组件的状态(useState),并在数据更新时调用此函数来重新计算。

总结

通过采用递归与广度优先遍历思想相结合的方法,我们能够有效地按层级汇总嵌套数据结构中的数值。这种方法清晰地分离了不同层级的处理逻辑,确保了结果的准确性。理解并掌握这种处理层级数据的模式,对于开发涉及树形结构的应用至关重要。

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

536

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

372

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

706

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

470

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

388

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

989

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

652

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

537

2023.09.20

苹果官网入口直接访问
苹果官网入口直接访问

苹果官网直接访问入口是https://www.apple.com/cn/,该页面具备0.8秒首屏渲染、HTTP/3与Brotli加速、WebP+AVIF双格式图片、免登录浏览全参数等特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

10

2025.12.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 2.9万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 0.9万人学习

React核心原理新老生命周期精讲
React核心原理新老生命周期精讲

共12课时 | 1.0万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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