JS将一个具有父子关系的一维数组转换为树形结构
// 数据项中,parentId指当前菜单的父菜单项的id。
const data = [
{ id: 1, title: '系统管理', parentId: 0 },
{ id: 2, title: '用户管理', parentId: 1 },
{ id: 3, title: '角色管理', parentId: 1 },
{ id: 4, title: '菜单管理', parentId: 1 },
{ id: 5, title: '字典管理', parentId: 1 },
{ id: 6, title: '编码规则管理', parentId: 1 },
{ id: 7, title: '个人中心', parentId: 0 },
{ id: 8, title: '个人资料', parentId: 7 },
{ id: 9, title: '我的消息', parentId: 7 },
{ id: 10, title: '菜单1', parentId: 0 },
{ id: 11, title: '菜单1-1', parentId: 10 },
{ id: 12, title: '菜单1-2', parentId: 10 },
{ id: 13, title: '菜单1-2-1', parentId: 12 },
{ id: 14, title: '菜单1-2-2', parentId: 12 },
{ id: 15, title: '菜单1-2-3', parentId: 12 }
]
转换结果
[
{
"id": 1,
"title": "系统管理",
"parentId": 0,
"children": [
{
"id": 2,
"title": "用户管理",
"parentId": 1,
"children": []
},
{
"id": 3,
"title": "角色管理",
"parentId": 1,
"children": []
},
{
"id": 4,
"title": "菜单管理",
"parentId": 1,
"children": []
},
{
"id": 5,
"title": "字典管理",
"parentId": 1,
"children": []
},
{
"id": 6,
"title": "编码规则管理",
"parentId": 1,
"children": []
}
]
},
{
"id": 7,
"title": "个人中心",
"parentId": 0,
"children": [
{
"id": 8,
"title": "个人资料",
"parentId": 7,
"children": []
},
{
"id": 9,
"title": "我的消息",
"parentId": 7,
"children": []
}
]
},
{
"id": 10,
"title": "菜单1",
"parentId": 0,
"children": [
{
"id": 11,
"title": "菜单1-1",
"parentId": 10,
"children": []
},
{
"id": 12,
"title": "菜单1-2",
"parentId": 10,
"children": [
{
"id": 13,
"title": "菜单1-2-1",
"parentId": 12,
"children": []
},
{
"id": 14,
"title": "菜单1-2-2",
"parentId": 12,
"children": []
},
{
"id": 15,
"title": "菜单1-2-3",
"parentId": 12,
"children": []
}
]
}
]
}
]
思路1:递归方式
- 首先定义一个 children 数组,用于存储所有与指定父节点 ID 匹配的子节点;
- 然后遍历整个数组,查找与指定父节点 ID 匹配的所有子节点;
- 对于每个匹配的子节点,递归调用 toTree 函数,传递当前子节点的 ID 作为新的父节点 ID,以获取当前子节点的所有子节点;
- 最后将所有子节点添加到 children 数组中,并返回结果数组。
function toTree(arr, parentId) {
if (!arr.length) {
return [];
}
// 定义一个 children 数组
const children = [];
for (const node of arr) {
if (node.parentId === parentId) {
const child = { ...node };
// 对于每个匹配的子节点,递归调用 toTree 函数,传递当前子节点的 ID 作为新的父节点 ID,以获取当前子节点的所有子节点。
child.children = toTree(arr, node.id);
children.push(child);
}
}
return children;
}
// 测试代码
const tree = toTree(data, 0);
console.log(tree);
思路2:非递归方式
- 首先创建一个空数组 tree 用于存储树形结构,同时创建一个 Map 对象 map 用于存储每个节点的子节点信息;
- 遍历数组中的每个节点,将其作为 map 中的一个数据项存储。此时每个节点的 children 属性被初始化为空数组;
- 再次遍历 map 中的每个节点。对于每个节点,获取其父节点 ID 并从 map 中查找父节点。如果找到父节点,则将该节点添加为父节点的子节点;否则将该节点添加到根节点数组 tree 中;
- 完成所有节点的处理后,返回 tree 数组作为结果。
function toTree(arr) {
const tree = [];
const map = new Map();
arr.forEach(node => {
map.set(node.id, { ...node, children: [] });
});
map.forEach((node, _, map) => {
const parentId = node.parentId;
const parent = map.get(parentId);
if (parent) {
parent.children.push(node);
} else {
tree.push(node);
}
});
return tree;
}
// 测试代码
const tree = toTree(data);
console.log(tree);
如何选择?
虽然递归方式实现简单,但时间复杂度和空间复杂度都较高,不适用于处理大规模的数据集。相比之下,非递归方式具有更好的性能和可扩展性,适合处理大规模的数据集。