我们在开发中经常会处理数组,特别是后端传给我们的扁平化数组,然后将它处理为多维结构的数组
将扁平化数组转换成树形结构数组的n中方法
原始数据
var arr = [
{ id: 1, pid: 0, text: '一级1' },
{ id: 2, pid: 0, text: '一级2' },
{ id: 3, pid: 1, text: '二级1-1' },
{ id: 4, pid: 1, text: '二级1-2' }
{ id: 5, pid: 2, text: '二级2-1' },
{ id: 6, pid: 2, text: '二级2-2' }
{ id: 7, pid: 3, text: '三级1-1-1' },
{ id: 8, pid: 7, text: '四级1-1-1-1' }
{ id: 9, pid: 8, text: '五级1-1-1-1-1' },
{ id: 10, pid: 9, text: '六级1-1-1-1-1' }
]
方法1
使用ES5的的数组新方法filter两层嵌套来实现
// 使用filter过滤方法
// 使用filter过滤方法
(function(){
let x = 0;
let arrayToThree = function(arr){
// 先把要格式化的数组进行深拷贝,由于这种方式是操作元素组的,所以需要先拷贝一份
let cloneArr = JSON.parse(JSON.stringify(arr));
// 然后使用filter进行过滤
return cloneArr.filter(p=>{
// 嵌套filter,为数组的每一项进行一次整个数组的判断
// 获取整个数组中是当前项的子集然后返回
const _getChild = cloneArr.filter((c) => {x++;return c.pid == p.id});
// 判断获取的数组是否有值如果有值,赋值给当前项的children
_getChild.length && (p.children = _getChild);
// 然后返回顶级的项
return p.pid === 0;
});
};
let getTree = arrayToThree(arr);
console.log(getTree);
console.log(x);
//100
// 此方法虽然最简单,但是循环次数却很多,虽然所有循环都是js内部处理
})();
方法2
(function(){
let x = 0;
let getArr = JSON.parse(JSON.stringify(arr));
// 定义方法
let arrayToTree = function(arr){
// 获取所有顶级分类,然后组成数组
let parents = arr.filter(v=> v.pid === 0);
// 获取所有非顶级分类,组成数组
let children = arr.filter(v => v.pid !== 0);
// 定义递归方法, 传入参数,父级数组,和子集数组
function dataToTree(p, c){
// 循环遍历父级数组
p.forEach(p_v =>{
// 循环遍历子集数组
c.forEach((c_v, c_i) => {
x++;
// 判断当前的子项是否为当前的父项的子元素
if(c_v.pid === p_v.id){
// 如果是那么判断当前,当前父类项是否有children 字段
if(!p_v.children){
// 如果没有初始化为空数组
p_v.children = [];
}
// 然后把当前的子类项推到父类的children数组中
p_v.children.push(c_v);
// 在执行递归调用当前方法,把当前子类项以数组的形式作为第一个参数传入,然后把所有的子集数组作为第二个参数传入,从新遍历所有子类数组中是否有当前子类的下一级
// dataToTree([c_v], children);
/* 为了减少遍历的次数可以把当前项从子类型中删除 */
// 然后把所有的子集数组深拷贝一份,因为数组中的项是对象属于引用类型的
let _c = JSON.parse(JSON.stringify(c));
//然后把当前子类项从整体的子类数组中剔除
_c.splice(c_i, 1);
// 在执行递归调用当前方法,把当前子类项以数组的形式作为第一个参数传入,然后把所有的子集数组作为第二个参数传入,从新遍历所有子类数组中是否有当前子类的下一级
dataToTree([c_v], _c);
}
});
});
}
dataToTree(parents, children);
return parents;
};
let getTree = arrayToTree(getArr);
console.log(getTree);
console.log(x);
// 62
})();
方法3
(function(){
let x = 0;
let getArr = JSON.parse(JSON.stringify(arr));
// 定义递归的方法
let arrayToTree = (arr, id) => {
// 使用ES6中数组的reduce方法来实现
// reduce遍历数组中的所有行,并且返回每一项合起来的结果
return arr.reduce((res, current)=>{
x++;
//判断当前值的pid是否和传入的id相同
if(current.pid === id){
// 如果相同直接给当前的项增加children属性,他的值是递归调用该方法返回的数组,从新递归调用该方法,把数组,和当前项的id作为参数传入
current.children = arrayToTree(arr, current.id);
// 返回拼接好的数组
return res.concat(current);
}
// 最后返回处理后的数组
return res;
}, []);
}
console.log(arrayToTree(getArr, 0));
console.log(x);
// 110
})();
方法4
(function(){
let x = 0;
var toTree = function(tarArray){
// 为了防止改变原数组,需要深拷贝一份数组
tarArray = JSON.parse(JSON.stringify(tarArray));
// 首先定义一个变量,把数组中的每一项以当前项的id为下标存入对象中
var obj = {}
// 遍历数组并存入项
tarArray.map((item,index)=>{
x++;
obj[item.id] = item;
});
// 定义一个新数组,用来保存生成的树形数组
var newArr = [];
//再次遍历数组进行检测
tarArray.forEach((item, index)=>{
x++;
// 先去刚才的对象中查找当前项有没有父级
let curItemParent = obj[item.pid];
if(curItemParent){
// 如果有那么就判断当前的父级有没有children属性,如果没有取空数组,如果有取本身children
curItemParent.children = curItemParent.children || [];
// 然后把当前项推入到他的父级的children 数组中
curItemParent.children.push(item);
}else{
// 如果没有证明这个是根节点,直接放到新数组中
newArr.push(item);
}
});
console.log(newArr)
}
toTree (arr);
console.log(x);
// 20
// 此方法是循环次数最少的,但是需要额外定义一个对象来做对比
})();
方法5
(function(){
let x = 0;
// 首先定义方法,传入要转换的数组,和根节点的pid号
var arrayToTree = function(arr, rootId){
// 这里主要是使用的es5新增的特性来深拷贝数组,如果做兼容需要使用深拷贝方法
arr = JSON.parse(JSON.stringify(arr));
//定义一个初始化对象,其中children用来存储树形化的数组
var newObj = {id: rootId, children: []};
// 定义一个递归的方法,并传入当前的父级项
var toTree = function(parObj){
// 获取数组的最后一个值得下标
var i = arr.length - 1;
// 获取当前父级项的id
var id = parObj.id;
// 从数组右边往左循环
while(i > -1){
x++;
//获取当前遍历的项
var item = arr[i];
// 判断当前项是否是当前父级项的子集
if(item.pid === id){
// 如果是,然后判断当前父级项是否有children字段,如果有直接用,如果没有给children赋值为一个空数组
parObj.children = parObj.children || [];
// 然后把当前项放到当前父级的children数组的最前方
parObj.children.unshift(item);
// 然后删除整个数组中的当前项,为了减少递归的循环次数提高性能
arr.splice(i, 1);
// 然后再调用方法本身,把当前项传入,进行下一轮的查找,找到数组中属于当前项的子集并进行操作
toTree(item);
}
i--;
}
};
// 初次调用传入初始化的对象
toTree(newObj);
return newObj.children;
};
console.log(arrayToTree(arr, 0));
console.log(x);
// 55
})();
方法6
(function(){
let x = 0;
// 首先定义方法,传入要转换的数组,和根节点的pid号
var arrayToTree = function(arr, rootId){
// 这里主要是使用的es5新增的特性来深拷贝数组,如果做兼容需要使用深拷贝方法
arr = JSON.parse(JSON.stringify(arr));
//定义一个初始化对象,其中children用来存储树形化的数组
var newObj = {id: rootId, children: []};
// 定义一个递归的方法,并传入当前的父级项
var toTree = function(parObj){
// 获取数组的最后一个值得下标
var i = 0;
// 获取当前父级项的id
var id = parObj.id;
// 从数组右边往左循环
while(i < arr.length){
x++;
//获取当前遍历的项
var item = arr[i];
// 判断当前项是否是当前父级项的子集
if(item.pid === id){
// 如果是,然后判断当前父级项是否有children字段,如果有直接用,如果没有给children赋值为一个空数组
parObj.children = parObj.children || [];
// 然后把当前项放到当前父级的children数组的最前方
parObj.children.push(item);
// 然后删除整个数组中的当前项,为了减少递归的循环次数提高性能
arr.splice(i, 1);
i--;
// 然后再调用方法本身,把当前项传入,进行下一轮的查找,找到数组中属于当前项的子集并进行操作
toTree(item);
}
i++;
}
};
// 初次调用传入初始化的对象
toTree(newObj);
return newObj.children;
};
console.log(arrayToTree(arr, 0));
console.log(x);
// 37
})();