问题描述
对于树结构的数据的加工,一般有以下几种情况
-
树结构--->树结构
- 树节点增加key和value
- 树节点删除key和value
- 树节点key和value颠倒变换
-
树结构--->扁平化数组
- 树结构拍平
-
扁平化数组--->树结构
- 如sql查出来的数据是扁平化的数据,注意pid和pids字段
- 后端把查出来的数据根据pids字段转成树结构(或者直接扔给前端前端来转树结构)
1. 树菜单更改其中的key、value
代码
<script>
let oldTree = [ // 后端返回很多数据,前端只使用其中一部分
{
name: '北京',
level: 2,
},
{
name: '上海',
level: 2,
children: [
{
name: '浦东新区',
level: 3,
},
{
name: '青浦区',
level: 3,
},
]
},
{
name: '广州',
level: 2,
children: [
{
name: '白云区',
level: 3,
}
]
},
]
let newTree = [] // newTree存放加工好前端使用的数据
function getTreeData(oldTree, newTree) {
oldTree.forEach((item) => {
let newTreeObj = {
label: item.name,
hierarchy: item.level
}
if (item.children) { // c.children.length>0
getTreeData(item.children, newTreeObj.children = [])
}
newTree.push(newTreeObj)
});
}
getTreeData(oldTree, newTree)
console.log(newTree)
</script>
效果图
2. 给树结构加上层级level字段
代码
let tree =
[
{
name: '中国',
children: [
{
name: '北京',
children: [
{
name: '朝阳区'
}
]
},
{
name: "上海",
children: [
{
name: '浦东新区'
}
]
}
]
}
]
function setTreeLevel(tree, level) {
level = level + 1 // 注意这里的层级字段增加需要在循环外边加
tree.forEach((item) => {
item.level = level
if (item.children) {
setTreeLevel(item.children, level)
}
});
return tree
}
let res = setTreeLevel(tree, 0)
console.log(res);
效果图
3. 递归方法将扁平化数组转树菜单树结构
数据库中查询的数据是一行行的,也就是一个数据,不存在children子节点相关,需要前端自己加工...
4. 使用递归将多层(维)数组拍平
多维数组拍平的思路:
- 遍历这个数组,得到数组中的每一项(有的项是普通的值,有的项仍旧是数组)
- 再定义一个数组
newArr,用于存储拍平后的数组值 - 如果是普通的项,就直接将这一项追加
push到newArr内即可 - 如果不是普通的项是数组(非数组就是普通的项)就递归遍历
写法一:单层函数
let arr = [1, [2, 3], [4, [5, [6]], 7]] // 定义一个多维数组
function isArr(params) { // 定义一个函数,用于判断是否是数组
return Object.prototype.toString.call(params) === "[object Array]"
}
function flatFn(arr, newArr = []) { // 递归函数拍平多维数组
arr.forEach((item) => {
if (!isArr(item)) { // 若这一项不是数组就追加进去。
newArr.push(item)
} else { // 若这一项是数组就递归继续追加
flatFn(item, newArr)
}
});
return newArr
}
let res = flatFn(arr)
console.log('多维数组拍平', res);
写法二:双层函数
let arr = [1, [2, 3], [4, [5, [6]], 7]] // 定义一个多维数组
function flatFn(arr) {
let newArr = []
function digui(arr) { // 专门定义递归函数用来递归拍平
arr.forEach((item) => {
if (Array.isArray(item)) { // 若是数组,继续递归拍平
digui(item)
} else { // 若不是数组直接追加即可
newArr.push(item)
}
});
}
digui(arr)
return newArr
}
let res = flatFn(arr)
console.log('递归拍平函数', res);
打印的结果
[1, 2, 3, 4, 5, 6, 7] // 这样就把数组拍平了
reduce方式的递归
let arrData = [1, [2, [3, 4, 5]]];
function flatFn(params, tempArr = []) {
let result = params.reduce((tempArr, item) => { // reduce函数需要一个临时变量,这里定义的临时变量是空数组
if (!Array.isArray(item)) { // 如果遍历的这一项不是数组,只是普通的项,直接push丢进去进去即可
tempArr.push(item)
return tempArr // reduce函数需要将临时变量return出去
} else {
// 如果是数组,就递归一下,flatFn接收两个参数,用于遍历的数组,以及用于存储进行拍平项的数组
return flatFn(item, tempArr)
}
}, tempArr)
// 经过上面一波操作完毕,得到拍平的结果
return result
}
let res = flatFn(arrData)
console.log('拍平', res);
判断是否是数组(知识点复习)
console.log(
"typeof", typeof [1, 2, 3] // object
);
console.log(
"constructor", [1,2,3].constructor === Array // true
);
console.log(
"instanceof", [1, 2, 3] instanceof Array // true
);
console.log(
"Object.prototype.toString.call(xxx)", Object.prototype.toString.call([1, 2, 3]) // [object Array]
);
console.log(
"Array.isArray(xxx)", Array.isArray([1, 2, 3]) // true
);
优先推荐Object.prototype.toString.call()和Array.isArray()
判断是否是对象(知识点复习)
let obj = { judge: 'Object' }
console.log(
'typeof', typeof obj // object
);
console.log(
'constructor', obj.constructor === Object // true
);
console.log(
'instanceof', obj instanceof Object // true
);
console.log(
'Object.prototype.toString.call', Object.prototype.toString.call(obj) // [object Object]
);
优先推荐Object.prototype.toString.call()和instanceof
5. 递归手写一个深拷贝
思路是:
- 判断要拷贝的数据是什么数据类型,是对象?是数组?或者是普通的数据
- 然后定义一个变量用来存储深拷贝的结果。
- 若是对象就
forin这个对象,赋值的时候要递归赋值; - 若是数组
for这个数组,数组追加push的时候要递归追加
let obj = {
name: '孙悟空',
age: 500,
like: {
eat: 'peach',
change: '72变'
},
partner: ['唐僧', '猪八戒', '沙和尚', '白龙马']
}
function deepClone(params) {
let result; // 1. 先定义一个变量(暂不确定这个变量的数据类型),最终是要将传进来的数据都拷贝在这个变量内
if (Object.prototype.toString.call(params) === "[object Object]") { // 2. 若传进来的数据类型是对象
result = {} // 3. 是对象的话,就把变量置为对象数据类型
for (const key in params) { // 4. 同时遍历传进来的这个对象
// result[key] = params[key] // 4.1 正常情况下直接赋值即可
result[key] = deepClone(params[key]) // 5. 但是考虑到直接赋值的值可能是对象(数组),所以需要递归一下
}
} else if (Array.isArray(params)) { // 6. 若传进来的数据类型是数组
result = [] // 7. 就把变量置为数组类型
for (let i = 0; i < params.length; i++) { // 8. 同时遍历传进来的这个数组
// result.push(params[i]) // 8.1 正常情况下直接push装一份数据即可
result.push(deepClone(params[i])) // 9. 但是考虑到装的这一项是数组或是(数组)对象,所以需要递归一下
}
} else {
result = params // 10. 最终会找到最里层的数据直接赋值
}
return result // 11. 最后将递归深拷贝的结果丢出去以供使用
}
let res = deepClone(obj)
console.log('深拷贝结果-->', res);
6. 递归删除对象中空属性(如post请求中的body对象参数)
工作中,给后端传参时,post请求,请求体传递一个对象,对象中如果有值是空的话,就不传递给后端了。所以在传参之前,要加工一下参数,如下代码:
let obj = {
aaa: '你好',
bbb: '',
ccc: null,
ddd: undefined,
eee: {
fff: '147',
ggg: [],
hhh: [1, 2, 3]
}
}
function deleteEmptyKey(obj) {
for (const key in obj) {
// 对象处理
if (Object.prototype.toString.call(obj[key]) === '[object Object]') {
if (JSON.stringify(obj[key]) === '{}') { // 空对象直接删除即可
delete obj[key]
} else { // 非空对象,递归继续操作
deleteEmptyKey(obj[key])
}
}
// 数组处理
else if (Array.isArray(obj[key])) {
if (obj[key].length == 0) { // 空数组也直接删除
delete obj[key]
} else {
// 非空数组就保留着,准备传递给后端
}
} else { // 普通数据类型处理
if (obj[key] === '' | obj[key] === null | obj[key] === undefined) { // 空值啥的不要,不传递给后端
delete obj[key]
}
}
}
return obj // 直接使用或者使用返回值均可
}
let res = deleteEmptyKey(obj)
console.log('删除对象中的空值', res);
7. 递归拼接完整路径
复制粘贴,运行直接看效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
let tree = [
{
name: '中国',
path: '/china',
children: [
{
name: '北京',
path: '/beijing',
children: [
{
name: '朝阳',
path: '/chaoyang',
},
{
name: '海淀',
path: '/haidian',
},
]
},
{
name: '上海',
path: '/shanghai',
children: [
{
name: '浦东',
path: '/pudong',
},
{
name: '静安',
path: '/jingan',
},
]
},
]
},
{
name: '美国',
path: '/american',
children: [
{
name: '纽约',
path: '/newYork',
children: [
{
name: '中城',
path: '/middleCity',
},
{
name: '下城',
path: '/downCity',
},
]
},
{
name: '洛杉矶',
path: '/losAngeles',
children: [
{
name: '长滩',
path: '/longBeach',
},
{
name: '圣安娜',
path: '/santaAna',
},
]
},
]
}
]
function addFullPath(oldTree, pre) {
oldTree.forEach((item) => {
item['fullPath'] = pre + '' + item.path
if (item.children) {
addFullPath(item.children, item.fullPath)
}
});
return oldTree
}
const result = addFullPath(tree, '')
console.log('加完整路径结果-->', result);
// 示例结果代码:
let r = [
{
"name": "中国",
"path": "/china",
"children": [
{
"name": "北京",
"path": "/beijing",
"children": [
{
"name": "朝阳",
"path": "/chaoyang",
"fullPath": "/china/beijing/chaoyang"
},
{
"name": "海淀",
"path": "/haidian",
"fullPath": "/china/beijing/haidian"
}
],
"fullPath": "/china/beijing"
},
{
"name": "上海",
"path": "/shanghai",
"children": [
{
"name": "浦东",
"path": "/pudong",
"fullPath": "/china/shanghai/pudong"
},
{
"name": "静安",
"path": "/jingan",
"fullPath": "/china/shanghai/jingan"
}
],
"fullPath": "/china/shanghai"
}
],
"fullPath": "/china"
},
{
"name": "美国",
"path": "/american",
"children": [
{
"name": "纽约",
"path": "/newYork",
"children": [
{
"name": "中城",
"path": "/middleCity",
"fullPath": "/american/newYork/middleCity"
},
{
"name": "下城",
"path": "/downCity",
"fullPath": "/american/newYork/downCity"
}
],
"fullPath": "/american/newYork"
},
{
"name": "洛杉矶",
"path": "/losAngeles",
"children": [
{
"name": "长滩",
"path": "/longBeach",
"fullPath": "/american/losAngeles/longBeach"
},
{
"name": "圣安娜",
"path": "/santaAna",
"fullPath": "/american/losAngeles/santaAna"
}
],
"fullPath": "/american/losAngeles"
}
],
"fullPath": "/american"
}
]
</script>
</body>
</html>