先说一下需求是什么意思,假设有下面的一组数据:
var list = [
{ id: 1013, pid: 0 },
{ id: 1014, pid: 0 },
{ id: 1061, pid: 1013 },
{ id: 1063, pid: 1014 },
{ id: 1066, pid: 1014 },
{ id: 1067, pid: 1066 }
]
现在要把它转为下面这样的树形结构:
var tree = [
{ id: 1013, pid: 0, children: [
{ id: 1061, pid: 1013 }
]},
{ id: 1014, pid: 0, children: [
{ id: 1063, pid: 1014 },
{ id: 1066, pid: 1014, children: [
{ id: 1067, pid: 1066 }
]}
]}
]
-
用 for 循环的方式
// 用for循环的方法
for (var i = 0; i < list.length; i++) {
for (var j = 0; j < list.length; j++) {
// 如果有父节点
if (list[i].pid === list[j].id) {
// 放进它父节点的children数组中;如果children不存在,初始化为空数组
list[j].children = list[j].children || []
list[j].children.push(list[i])
// 因为每个节点至多有一个父节点,所以这里可以退出本次循环,避免无z意义的运算
break
}
}
// 如果j的值等于list的长度,说明在内层循环中没有触发break,也就是说这个节点是根节点
if (j === list.length) tree.push(list[i])
}
- 用数组对象中的方法
// filter筛选符合条件的元素,返回一个包含所有符合条件的元素的新数组
tree = list.filter(item1 => !list.find((item2, index) => {
// 如果有父节点
if (item1.pid === item2.id) {
// 放进它父节点的children数组中;如果children不存在,初始化为空数组
list[index].children = list[index].children || []
list[index].children.push(item1)
}
// find返回第一个符合条件的元素,找到后,剩余的元素不再判断
return item1.pid === item2.id
}))
解读
以上两个方法都利用了引用类型数据的特点,当我们创建一个保存引用类型数据的变量时,实际上我们保存的是它在内存中的地址,如果你的入门语言是 C,那么你一定对指针不陌生,JavaScript 的引用类型在这一点上与 C 的指针类似。
上面的代码中,每次向 children 中 push 数据时,我们 push 的都是“地址”,所以只需要通过一次遍历,将所有非根节点放到它的父节点中,就能得到树形结构。
可以看到,上面的代码都对原数据 list 做了操作,如果不想改变原数据,需要对 list 进行深拷贝,直接这样写是没有用的:
var tree = list
原因正是上面提到的引用类型,如何做深拷贝,那就是另一个故事了。