
在前端开发中,我们经常会遇到需要管理层级关系的数据,例如文件系统、组织架构或分类目录。这类数据通常以嵌套的数组或对象形式表示。本教程关注的是一种特定的树形结构,其中每个节点包含一个唯一的 key、名称 name、当前值 curr、总值 total,以及一个 nodes 数组来表示其子节点。
以下是一个典型的嵌套数据结构示例:
const data = [
{
key: "id1",
name: "Category 1",
curr: 0,
total: 0,
nodes: [
{
key: "id2",
name: "Applications",
curr: 20,
total: 30,
nodes: [
{
key: "id3",
name: "Gaming",
curr: 5,
total: 10,
nodes: []
},
{
key: "id4",
name: "Operating System",
curr: 15,
total: 20,
nodes: []
}
]
}
]
},
{
key: "id5",
name: "Category 2",
curr: 0,
total: 0,
nodes: [
{
key: "id6",
name: "Sub Category",
curr: 12,
total: 48,
nodes: [
{
key: "id7",
name: "Inside Sub",
curr: 12,
total: 48,
nodes: []
}
]
}
]
},
{
key: "id8",
name: "Last One",
curr: 0,
total: 0,
nodes: []
}
];我们的目标是:给定一个 key 和上述数据数组,找到匹配 key 的节点,将其 curr 值递增1。同时,这个递增操作需要向上级联,使其所有祖先节点的 curr 值也递增1。但有一个关键限制:最顶层(根级别,即 data 数组中的直接元素)的节点不应该被更新。
例如,如果调用 incrementRecursively(data, "id4"),期望的输出如下:
const output = [
{
key: "id1",
name: "Category 1",
curr: 0, // 注意:id1 是根节点,不更新
total: 0,
nodes: [
{
key: "id2",
name: "Applications",
curr: 21, // id2 是 id4 的祖先,更新
total: 30,
nodes: [
{
key: "id3",
name: "Gaming",
curr: 5,
total: 10,
nodes: []
},
{
key: "id4",
name: "Operating System",
curr: 16, // id4 是目标节点,更新
total: 20,
nodes: []
}
]
}
]
},
// ... 其他节点保持不变
];传统的递归遍历方法通常是从上到下执行操作。然而,本问题要求在找到目标节点后,将更新信息从下往上传递给祖先节点。同时,还需要区分不同层级的节点,以避免更新根节点。
立即学习“Java免费学习笔记(深入)”;
一个常见的尝试是使用 forEach 结合递归:
const incrementRecursively = (nodes, key) => {
const increment = (nodes, key) => {
nodes.forEach((node) => {
if (node.key === key) {
node.curr++; // 仅更新目标节点
}
if (node.nodes && node.nodes.length) { // 检查 node.nodes 是否存在且有元素
increment(node.nodes, key); // 递归调用,但没有向上级联的能力
}
});
};
increment(nodes, key);
return nodes;
};上述代码的问题在于:
为了解决上述挑战,我们可以设计一个递归函数,该函数在找到目标节点或其子节点包含目标节点时,返回一个布尔值 true。这个布尔值将作为信号,指示其父节点也需要执行更新操作。同时,通过引入一个 depth 参数来跟踪当前节点的层级,我们可以轻松地排除根节点的更新。
核心思路:
/**
* 递归更新树形结构中指定key的节点及其所有祖先节点的curr值,但不更新根节点。
* @param {Array<Object>} nodes - 当前层级的节点数组。
* @param {string} key - 需要更新的目标节点的key。
* @param {number} [depth=0] - 当前递归的深度,根节点深度为0。
* @returns {boolean} 如果当前节点或其子节点被更新,则返回true;否则返回false。
*/
function updateParentChildCurr(nodes, key, depth = 0) {
// 遍历当前层级的每个节点
for (let node of nodes) {
// 检查当前节点是否是目标节点,
// 或者递归调用子节点,如果子节点中找到了目标并返回了true
if (node.key === key || updateParentChildCurr(node.nodes ?? [], key, depth + 1)) {
// 如果当前节点或其子节点被更新,并且当前节点不是根节点(depth > 0)
if (depth > 0) {
node.curr++; // 递增当前节点的curr值
}
return true; // 向上传播信号:此路径上的节点已被更新
}
}
return false; // 当前层级没有找到目标节点或其子节点
}
// 示例数据
const data = [
{ key: "id1", name: "Category 1", curr: 0, total: 0, nodes: [ { key: "id2", name: "Applications", curr: 20, total: 30, nodes: [ { key: "id3", name: "Gaming", curr: 5, total: 10, nodes: [] }, { key: "id4", name: "Operating System", curr: 15, total: 20, nodes: [] } ] } ] },
{ key: "id5", name: "Category 2", curr: 0, total: 0, nodes: [ { key: "id6", name: "Sub Category", curr: 12, total: 48, nodes: [ { key: "id7", name: "Inside Sub", curr: 12, total: 48, nodes: [] } ] } ] },
{ key: "id8", name: "Last One", curr: 0, total: 0, nodes: [] }
];
// 调用函数进行更新
// updateParentChildCurr(data, 'id4');
// console.log("更新id4后的数据:", JSON.stringify(data, null, 2));
// 再次调用,例如更新id7
// updateParentChildCurr(data, 'id7');
// console.log("更新id7后的数据:", JSON.stringify(data, null, 2));function updateParentChildCurr(nodes, key, depth = 0):
for (let node of nodes): 遍历当前 nodes 数组中的每一个节点。
if (node.key === key || updateParentChildCurr(node.nodes ?? [], key, depth + 1)):
if (depth > 0):
node.curr++: 递增当前节点的 curr 值。
return true: 如果 if 条件为真(即当前节点或其子孙节点被更新),则函数返回 true。这个 true 会被其父节点的递归调用接收,从而触发父节点的更新逻辑,形成自下而上的级联更新。
return false: 如果遍历完当前层级的所有节点,都没有找到目标节点,也没有任何子节点被更新,则返回 false。
通过巧妙地结合递归调用和布尔返回值,我们成功地实现了一个能够在树形结构中自下而上地更新指定节点及其所有祖先节点 curr 值的函数,同时满足了排除根节点更新的特殊要求。这种模式在处理树形数据时非常有用,尤其当需要根据子节点状态来更新父节点状态时。理解 depth 参数和布尔信号的传递机制是掌握此解决方案的关键。
以上就是JavaScript树形结构中递归更新父子节点数据教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号