在平时的业务中,相信我们一定都遇到过需要将后台传过来的一维数组转为树结构。
var arr = [
{ id: 1, pid: -1, },
{ id: 2, pid: -1, },
{ id: 3, pid: -1, },
{ id: 4, pid: 1, },
{ id: 5, pid: 2, },
{ id: 6, pid: 3, },
{ id: 7, pid: 5, },
];
// 转换之后的树结构
var tree = [
{
"id": 1,
"pid": -1,
"children": [
{
"id": 4,
"pid": 1
}
]
},
{
"id": 2,
"pid": -1,
"children": [
{
"id": 5,
"pid": 2,
"children": [
{
"id": 7,
"pid": 5
}
]
}
]
},
{
"id": 3,
"pid": -1,
"children": [
{
"id": 6,
"pid": 3
}
]
}
]
复制代码
简单的方法
前提是层级固定,比如最多只有3层,那么我们可能会写出如下的代码:
var arr = [
{ id: 1, pid: -1, },
{ id: 2, pid: -1, },
{ id: 3, pid: -1, },
{ id: 4, pid: 1, },
{ id: 5, pid: 2, },
{ id: 6, pid: 3, },
{ id: 7, pid: 5, },
];
function toTree(data) {
var arr = [...data];
// 查找一级
var tree = arr.filter(v => v.pid == -1);
tree.map(v => {
// 查找二级
v.children = arr.filter(({ pid }) => pid == v.id);
if (v.children.length == 0) delete v.children;
else {
v.children.map(item => {
// 查找三级
item.children = arr.filter(({ pid }) => pid == item.id);
if (item.children.length == 0) delete item.children;
return item;
});
}
return v;
});
return tree;
}
JSON.stringify(toTree(arr), null, ' ')
复制代码
如果树结构的层级固定,我们可以直接使用filter和map通过简单的循环来生成树结构。如此一来,代码是简单了,效率却非常的低,因为确定每个元素的父子关系时都得遍历一次原数组,当原数组很长的时候,执行时间就会变得很长。
另外一个问题就是,树结构的层级被写死了,当树结构的深度大于我们的层级时,无法递归生成树结构。
那么,怎么解决这个问题呢?
可共用的函数
后来我想到了一个办法,就是
- 先确定第一层的子节点A,在确定的时候就将其从原数组中删去。
- 此时如果原数组非空,再遍历所有的子节点A,查找属于它的子节点,并将这些元素从原数组中删去,此时获得了A的子节点B;如果为空,就停止递归。
- 此时再判断原数组是否非空,是的话再遍历所有的子节点B,查找属于它的子节点,并将这些元素从原数组中删去;如果为空,就停止递归。
- 递归判断即可。
这里主要使用的就是数组的splice方法,让一维数组逐渐减少,直至数组为空结束递归。
还有一个知识点就是**使用了对象的引用,在没有拷贝对象的情况,修改函数传过去的对象就可以改变原对象。**平时讲究的都是纯函数,这里则恰好借助了非纯函数。
代码如下:
var arr = [
{ id: 1, pid: -1, },
{ id: 2, pid: -1, },
{ id: 3, pid: -1, },
{ id: 4, pid: 1, },
{ id: 5, pid: 2, },
{ id: 6, pid: 3, },
{ id: 7, pid: 5, },
];
// @params data 一维数组
// @params pid 父id字段名
// @params id id字段名
// @return Array
function toTree(data, pid = 'pid', id = 'id') {
var arr = [...data];
var tree = [];
for(let i = 0, len = arr.length; i < len; i++) {
if (arr[i][pid] == -1) {
tree.push(arr[i]);
arr.splice(i, 1);
// 这里注意,删除数组的其中一个元素后,下次索引不要 +1 即可访问下一个元素,所以这里 -1 以抵消 +1
i = i - 1;
}
}
getChild(arr, tree, pid, id);
return tree;
}
function getChild(originArr, mapArr, pid, id) {
// 如果一维数组为空,则结束递归
if (originArr.length == 0 || !mapArr) return ;
for(let j = 0, len = mapArr.length; j < len; j++) {
let item = mapArr[j];
item.children = [];
for(let i = 0; i < originArr.length; i++) {
let obj = originArr[i];
if (item[id] == obj[pid]) {
item.children.push(obj);
originArr.splice(i, 1);
// 这里注意,删除数组的其中一个元素后,下次索引不要 +1 即可访问下一个元素,所以这里 -1 以抵消 +1
i = i - 1;
}
}
// 如果没有子节点,就删除children属性
if (item.children.length == 0) {
delete item.children;
} else {
// 如果有子节点,就递归获取下一级子节点
getChild(originArr, item.children, pid, id);
}
}
}
toTree(arr)
JSON.stringify(toTree(arr), null, ' ')
复制代码