前言
樹結構的資料操作對於一個開發者來說是一個必備的技能。在實際的業務開發中,我們也會遇到許多樹結構的體現,比如最常見的地域樹,以及企業結構樹、校級組織樹等等。
下面整理了一系列的關於JavaScript樹的操作方法,結合示例,,相信大家在實際開發工作中或多或少都會用到。
陣列扁平化
示例
const arr = [1, [2, [3, 4]], 5, [6]];
方法
1、遞迴
const flatten = (arr) => {
let res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
2、reduce
const flatten = (arr) => {
return arr.reduce((result, item)=> {
return result.concat(Array.isArray(item) ? flatten(item) : item);
}, []);
}
3、flat
const flatten = (arr) => {
return arr.flat(Infinity)
}
執行結果
const result = flatten(arr);
console.log(result);
// 執行結果
[1, 2, 3, 4, 5, 6]
陣列轉樹形結構
示例
const arr = [
{
name: '小明',
id: 1,
pid: 0,
},
{
name: '小花',
id: 11,
pid: 1,
},
{
name: '小華',
id: 111,
pid: 11,
},
{
name: '小李',
id: 112,
pid: 11,
},
{
name: '小紅',
id: 12,
pid: 1,
},
{
name: '小王',
id: 2,
pid: 0,
},
{
name: '小林',
id: 21,
pid: 2,
},
{
name: '小李',
id: 22,
pid: 2,
}
]
方法
1、非遞迴
const arrayToTree = (arr) => {
let result = [];
if (!Array.isArray(arr) || arr.length === 0) {
return result
}
let map = {};
arr.forEach(item => map[item.id] = item);
arr.forEach(item => {
const parent = map[item.pid];
if(parent){
(parent.children || (parent.children=[])).push(item);
} else {
result.push(item);
}
})
return result
}
2、遞迴
const arrayToTree = (arr, pid) => {
let res = [];
arr.forEach(item => {
if(item.pid === pid){
let itemChildren = arrayToTree(arr,item.id);
if(itemChildren.length) {
item.children = itemChildren;
}
res.push(item);
}
});
return res;
}
執行結果
// const result = arrayToTree(arr);
const result = arrayToTree(arr, 0);
console.log(result);
// 執行結果
[
{
"name": "小明",
"id": 1,
"pid": 0,
"children": [
{
"name": "小花",
"id": 11,
"pid": 1,
"children": [
{
"name": "小華",
"id": 111,
"pid": 11
},
{
"name": "小李",
"id": 112,
"pid": 11
}
]
},
{
"name": "小紅",
"id": 12,
"pid": 1
}
]
},
{
"name": "小王",
"id": 2,
"pid": 0,
"children": [
{
"name": "小林",
"id": 21,
"pid": 2
},
{
"name": "小李",
"id": 22,
"pid": 2
}
]
}
]
樹形結構轉陣列(扁平化)
示例
const tree = [
{
name: '小明',
id: 1,
pid: 0,
children: [
{
name: '小花',
id: 11,
pid: 1,
children: [
{
name: '小華',
id: 111,
pid: 11,
},
{
name: '小李',
id: 112,
pid: 11,
}
]
},
{
name: '小紅',
id: 12,
pid: 1,
}
]
},
{
name: '小王',
id: 2,
pid: 0,
children: [
{
name: '小林',
id: 21,
pid: 2,
},
{
name: '小李',
id: 22,
pid: 2,
}
]
}
]
方法
1、深度優先遍歷
const treeToArray = (tree) => {
let stack = tree,
result = [];
while(stack.length !== 0){
let pop = stack.pop();
result.push({
id: pop.id,
name: pop.name,
pid: pop.pid
})
let children = pop.children
if(children){
for(let i = children.length-1; i >=0; i--){
stack.push(children[i])
}
}
}
return result
}
2、廣度優先遍歷
const treeToArray = (tree) => {
let queue = tree,
result = [];
while(queue.length !== 0){
let shift = queue.shift();
result.push({
id: shift.id,
name: shift.name,
pid: shift.pid
})
let children = shift.children
if(children){
for(let i = 0; i < children.length; i++){
queue.push(children[i])
}
}
}
return result
}
3、不用考慮除children外的其他屬性
const treeToArray = (source)=>{
let res = []
source.forEach(item=>{
res.push(item)
item.children && res.push(...treeToArray(item.children))
})
return res.map((item) => {
if (item.children) {
delete item.children
}
return item
})
}
執行結果
const result = treeToArray(tree);
console.log(result);
// 執行結果
[
{
"name": "小明",
"id": 1,
"pid": 0
},
{
"name": "小花",
"id": 11,
"pid": 1
},
{
"name": "小華",
"id": 111,
"pid": 11
},
{
"name": "小李",
"id": 112,
"pid": 11
},
{
"name": "小紅",
"id": 12,
"pid": 1
},
{
"name": "小王",
"id": 2,
"pid": 0
},
{
"name": "小林",
"id": 21,
"pid": 2
},
{
"name": "小李",
"id": 22,
"pid": 2
}
]
樹篩選,保留符合條件的資料並返回樹結構
示例
const tree = [
{
name: '小明',
id: 1,
pid: 0,
show: true,
children: [
{
name: '小花',
id: 11,
pid: 1,
show: true,
children: [
{
name: '小華',
id: 111,
pid: 11,
},
{
name: '小李',
id: 112,
pid: 11,
show: true,
}
]
},
{
name: '小紅',
id: 12,
pid: 1,
}
]
},
{
name: '小王',
id: 2,
pid: 0,
show: true,
children: [
{
name: '小林',
id: 21,
pid: 2,
},
{
name: '小李',
id: 22,
pid: 2,
}
]
}
]
方法
篩選出show為true資料
const filterTreeByFunc = (tree, func) => {
if (!Array.isArray(tree) || tree.length === 0) {
return []
}
return tree.filter(item => {
item.children = item.children && filterTreeByFunc(item.children, func)
return func(item) || (item.children && item.children.length)
})
}
const func = (item) => {
return item.show === true
}
執行結果
const result = filterTreeByFunc(tree, func);
console.log(result);
// 執行結果
[
{
"name": "小明",
"id": 1,
"pid": 0,
"show": true,
"children": [
{
"name": "小花",
"id": 11,
"pid": 1,
"show": true,
"children": [
{
"name": "小李",
"id": 112,
"pid": 11,
"show": true
}
]
}
]
},
{
"name": "小王",
"id": 2,
"pid": 0,
"show": true,
"children": []
}
]
查詢某一節點在樹中路徑
示例
const tree = [
{
name: '小明',
id: 1,
pid: 0,
children: [
{
name: '小花',
id: 11,
pid: 1,
children: [
{
name: '小華',
id: 111,
pid: 11,
},
{
name: '小李',
id: 112,
pid: 11,
}
]
},
{
name: '小紅',
id: 12,
pid: 1,
}
]
},
{
name: '小王',
id: 2,
pid: 0,
children: [
{
name: '小林',
id: 21,
pid: 2,
},
{
name: '小李',
id: 22,
pid: 2,
}
]
}
]
方法
const getNodePath = (tree, id) => {
if (!Array.isArray(tree) || tree.length === 0) {
return []
}
const path = []
const treeFindPath = (tree, id, path) => {
for (const item of tree) {
path.push(item.id);
if (item.id === id) {
return path
}
if (item.children) {
const findChildren = treeFindPath(item.children,id, path);
if (findChildren.length) {
return findChildren;
}
}
path.pop();
}
return [];
}
return treeFindPath(tree, id, path)
}
執行結果
const result = getNodePath(tree, 112);
console.log(result);
// 執行結果
[1, 11, 112]
模糊查詢樹
示例
const tree = [
{
name: '小明前端專家',
id: 1,
pid: 0,
children: [
{
name: '小花前端程式媛',
id: 11,
pid: 1,
children: [
{
name: '小華划水運動員',
id: 111,
pid: 11,
},
{
name: '小李摸魚運動員',
id: 112,
pid: 11,
}
]
},
{
name: '小紅摸魚程式設計師',
id: 12,
pid: 1,
}
]
},
{
name: '小王內卷王',
id: 2,
pid: 0,
children: [
{
name: '小林摸魚王',
id: 21,
pid: 2,
},
{
name: '小李後端程式設計師',
id: 22,
pid: 2,
}
]
}
]
方法
const fuzzyQueryTree = (arr, value) => {
if (!Array.isArray(arr) || arr.length === 0) {
return []
}
let result = [];
arr.forEach(item => {
if (item.name.indexOf(value) > -1) {
const children = fuzzyQueryTree(item.children, value);
const obj = { ...item, children }
result.push(obj);
} else {
if (item.children && item.children.length > 0) {
const children = fuzzyQueryTree(item.children, value);
const obj = { ...item, children }
if (children && children.length > 0) {
result.push(obj);
}
}
}
});
return result;
};
執行結果
const result = fuzzyQueryTree(tree,'程式');
console.log(result);
// 執行結果
[
{
"name": "小明前端專家",
"id": 1,
"pid": 0,
"children": [
{
"name": "小花前端程式媛",
"id": 11,
"pid": 1,
"children": []
},
{
"name": "小紅摸魚程式設計師",
"id": 12,
"pid": 1,
"children": []
}
]
},
{
"name": "小王內卷王",
"id": 2,
"pid": 0,
"children": [
{
"name": "小李後端程式設計師",
"id": 22,
"pid": 2,
"children": []
}
]
}
]
樹節點新增屬性
示例
const tree = [
{
name: '小明',
id: 1,
pid: 0,
children: [
{
name: '小花',
id: 11,
pid: 1,
children: [
{
name: '小華',
id: 111,
pid: 11,
},
{
name: '小李',
id: 112,
pid: 11,
}
]
},
{
name: '小紅',
id: 12,
pid: 1,
}
]
},
{
name: '小王',
id: 2,
pid: 0,
children: [
{
name: '小林',
id: 21,
pid: 2,
},
{
name: '小李',
id: 22,
pid: 2,
}
]
}
]
方法
const addAttrToNodes = (tree) => {
tree.forEach((item) => {
item.title = '新生代農民工'
if (item.children && item.children.length > 0) {
addAttrToNodes(item.children)
}
})
return tree
}
執行結果
const result = addAttrToNodes(tree);
console.log(result);
// 執行結果
[
{
"name": "小明",
"id": 1,
"pid": 0,
"children": [
{
"name": "小花",
"id": 11,
"pid": 1,
"children": [
{
"name": "小華",
"id": 111,
"pid": 11,
"title": "新生代農民工"
},
{
"name": "小李",
"id": 112,
"pid": 11,
"title": "新生代農民工"
}
],
"title": "新生代農民工"
},
{
"name": "小紅",
"id": 12,
"pid": 1,
"title": "新生代農民工"
}
],
"title": "新生代農民工"
},
{
"name": "小王",
"id": 2,
"pid": 0,
"children": [
{
"name": "小林",
"id": 21,
"pid": 2,
"title": "新生代農民工"
},
{
"name": "小李",
"id": 22,
"pid": 2,
"title": "新生代農民工"
}
],
"title": "新生代農民工"
}
]
樹節點刪除屬性
示例
這裡直接使用上面——樹形結構節點新增屬性的執行結果
方法
const removeAttrFromNode = (tree) => {
tree.forEach((item) => {
delete item.title
if (item.children && item.children.length > 0) {
removeAttrFromNode(item.children)
}
})
return tree
}
執行結果
const result = removeAttrFromNode(tree);
console.log(result);
// 執行結果
[
{
"name": "小明",
"id": 1,
"pid": 0,
"children": [
{
"name": "小花",
"id": 11,
"pid": 1,
"children": [
{
"name": "小華",
"id": 111,
"pid": 11
},
{
"name": "小李",
"id": 112,
"pid": 11
}
]
},
{
"name": "小紅",
"id": 12,
"pid": 1
}
]
},
{
"name": "小王",
"id": 2,
"pid": 0,
"children": [
{
"name": "小林",
"id": 21,
"pid": 2
},
{
"name": "小李",
"id": 22,
"pid": 2
}
]
}
]
刪除樹中的空children
示例
const tree = [
{
name: '小明',
id: 1,
pid: 0,
children: [
{
name: '小花',
id: 11,
pid: 1,
children: [
{
name: '小華',
id: 111,
pid: 11,
},
{
name: '小李',
id: 112,
pid: 11,
children: []
}
]
},
{
name: '小紅',
id: 12,
pid: 1,
children: []
}
]
},
{
name: '小王',
id: 2,
pid: 0,
children: [
{
name: '小林',
id: 21,
pid: 2,
},
{
name: '小李',
id: 22,
pid: 2,
children: []
}
]
}
]
方法
const removeEmptyChildren = (tree) => {
tree.forEach((item) => {
if (item.children && item.children.length ===0) {
delete item.children
} else if (item.children && item.children.length > 0) {
removeEmptyChildren(item.children)
}
})
return tree
}
執行結果
const result = removeEmptyChildren(tree);
console.log(result);
// 執行結果
[
{
"name": "小明",
"id": 1,
"pid": 0,
"children": [
{
"name": "小花",
"id": 11,
"pid": 1,
"children": [
{
"name": "小華",
"id": 111,
"pid": 11
},
{
"name": "小李",
"id": 112,
"pid": 11
}
]
},
{
"name": "小紅",
"id": 12,
"pid": 1
}
]
},
{
"name": "小王",
"id": 2,
"pid": 0,
"children": [
{
"name": "小林",
"id": 21,
"pid": 2
},
{
"name": "小李",
"id": 22,
"pid": 2
}
]
}
]
獲取樹中所有的葉子節點
示例
const tree = [
{
name: '小明',
id: 1,
pid: 0,
children: [
{
name: '小花',
id: 11,
pid: 1,
children: [
{
name: '小華',
id: 111,
pid: 11,
},
{
name: '小李',
id: 112,
pid: 11,
}
]
},
{
name: '小紅',
id: 12,
pid: 1,
}
]
},
{
name: '小王',
id: 2,
pid: 0,
children: [
{
name: '小林',
id: 21,
pid: 2,
},
{
name: '小李',
id: 22,
pid: 2,
}
]
}
]
方法
const getAllLeaf = (tree) => {
const result = []
const getLeaf = (tree) => {
tree.forEach((item) => {
if (!item.children) {
result.push(item)
} else {
getLeaf(item.children)
}
})
}
getLeaf(tree)
return result
}
執行結果
const result = getAllLeaf(tree);
console.log(result);
// 執行結果
[
{
"name": "小華",
"id": 111,
"pid": 11
},
{
"name": "小李",
"id": 112,
"pid": 11
},
{
"name": "小紅",
"id": 12,
"pid": 1
},
{
"name": "小林",
"id": 21,
"pid": 2
},
{
"name": "小李",
"id": 22,
"pid": 2
}
]
參考
https://www.cnblogs.com/mengf...
https://blog.csdn.net/susuzhe...
https://blog.csdn.net/web_yue...
最後
本文整理了一系列的關於JavaScript樹的操作方法,相當於平時的一個總結。大家可以拿來即用,或者根據實際的業務進行參考修改。
如果大家有更好的實現方式,或者自己在開發中遇到的,但是上面沒有涉及到的,歡迎提出來,大家一起討論一起進步~