JS 樹與數(shù)組相互轉(zhuǎn)換的幾種方法
前言
在前端業(yè)務中,后端返回的扁平化數(shù)組(Array)往往需要轉(zhuǎn)換為樹形結(jié)構(gòu)(Tree)來適配 UI 組件(如 Element UI 的 Tree 或 Cascader)。掌握多種轉(zhuǎn)換思路及性能差異,是進階高級前端的必備技能。
一、 核心概念:結(jié)構(gòu)對比
數(shù)組結(jié)構(gòu):每一項通過 parentId 指向父級。
const nodes = [
{ id: 3, name: '節(jié)點C', parentId: 1 },
{ id: 6, name: '節(jié)點F', parentId: 3 },
{ id: 0, name: 'root', parentId: null },
{ id: 1, name: '節(jié)點A', parentId: 0 },
{ id: 8, name: '節(jié)點H', parentId: 4 },
{ id: 4, name: '節(jié)點D', parentId: 1 },
{ id: 2, name: '節(jié)點B', parentId: 0 },
{ id: 5, name: '節(jié)點E', parentId: 2 },
{ id: 7, name: '節(jié)點G', parentId: 2 },
{ id: 9, name: '節(jié)點I', parentId: 5 },
];
樹形結(jié)構(gòu):父級通過 children 數(shù)組包裹子級。
let tree = [
{
id: 1,
name: 'text1',
parentId: 1,
children: [
{
id: 2,
name: 'text2',
parentId: 1,
children: [
{
id: 4,
name: 'text4',
parentId: 2,
},
],
},
{
id: 3,
name: 'text3',
parentId: 1,
},
],
},
];
二、 數(shù)組轉(zhuǎn)樹
1. 遞歸思路
原理:
- 首先需要傳遞給函數(shù)兩個參數(shù):數(shù)組、當前的父節(jié)點id
- 設置一個結(jié)果數(shù)組res,遍歷數(shù)組,先找到子元素的父節(jié)點id與父節(jié)點id一致的子項
- 將這個子項的id作為父節(jié)點id傳入函數(shù),繼續(xù)遍歷
- 將遍歷的結(jié)果作為children返回,并給當前項添加children
- 將這個當前項,插入到res里面,并返回
注意:如果不想影響原數(shù)組,需要先深拷貝一下數(shù)組。const cloneArr = JSON.parse(JSON.stringify (arr))
const nodes = [
{ id: 3, name: '節(jié)點C', parentId: 1 },
{ id: 6, name: '節(jié)點F', parentId: 3 },
{ id: 0, name: 'root', parentId: null },
{ id: 1, name: '節(jié)點A', parentId: 0 },
{ id: 8, name: '節(jié)點H', parentId: 4 },
{ id: 4, name: '節(jié)點D', parentId: 1 },
{ id: 2, name: '節(jié)點B', parentId: 0 },
{ id: 5, name: '節(jié)點E', parentId: 2 },
{ id: 7, name: '節(jié)點G', parentId: 2 },
{ id: 9, name: '節(jié)點I', parentId: 5 },
];
//遞歸寫法
const arrToTree1 = (arr, id) => {
const res = [];
arr.forEach((item) => {
if (item.parentId === id) {
const children = arrToTree1(arr, item.id);
//如果希望每個元素都有children屬性,可以直接賦值
if (children.length !== 0) {
item.children = children;
}
res.push(item);
}
});
return res;
};
console.log(arrToTree1(nodes, null));
2. 非遞歸思路
原理:利用 filter 進行二次篩選。雖然寫法簡潔,但在大數(shù)據(jù)量下性能較差(O(n2)O(n^2)O(n2))。
- 函數(shù)只需要接受一個參數(shù),也就是需要轉(zhuǎn)換的數(shù)組arr
- 第一層過濾數(shù)組,直接返回一個parentId為根id的元素
- 但是在返回之間,需要再根據(jù)當前id過濾里面的每一項(過濾規(guī)則為如果子項的paentId為當前的id,則在當前項的children插入這個子項)
const arrToTree2 = (arr) => {
return arr.filter((father) => {
const childrenArr = arr.filter((children) => {
return children.parentId === father.id;
});
//如果希望每個元素都有children屬性,可以直接賦值
if (childrenArr.length !== 0) {
father.children = childrenArr;
}
return father.parentId === null;
});
};
console.log(arrToTree2(nodes));
3. Map 對象方案(O(n)O(n)O(n)時間復雜度)
原理:利用對象的引用性質(zhì)。先將數(shù)組轉(zhuǎn)為 Map,再遍歷一次即可完成。這是在大數(shù)據(jù)量下的首選方案。
const arrToTree3 = (arr) => {
const map = {};
const res = [];
// 1. 建立映射表
arr.forEach((item) => {
map[item.id] = { ...item, children: [] };
});
// 2. 組裝樹結(jié)構(gòu)
arr.forEach((item) => {
const node = map[item.id];
if (item.parentId === null) {
res.push(node);
} else {
if (map[item.parentId]) {
map[item.parentId].children.push(node);
}
}
});
return res;
};
console.log(arrToTree3(nodes));
三、 樹轉(zhuǎn)數(shù)組
1. 遞歸遍歷思路
原理:定義一個結(jié)果數(shù)組,遞歸遍歷樹的每一層,將節(jié)點信息(排除 children)推入數(shù)組。
- 首先定義一個結(jié)果數(shù)組res,遍歷傳入的樹
- 直接將當前項的id、name、parentId包裝在一個新對象里插入
- 判斷是否有children屬性,如果有則遍歷children屬性每一項,繼續(xù)執(zhí)行2、3步驟
let tree = [
{
id: 1,
name: 'text1',
parentId: 1,
children: [
{
id: 2,
name: 'text2',
parentId: 1,
children: [
{
id: 4,
name: 'text4',
parentId: 2,
},
],
},
{
id: 3,
name: 'text3',
parentId: 1,
},
],
},
];
const treeToArr = (tree) => {
const res = [];
tree.forEach((item) => {
const loop = (data) => {
res.push({
id: data.id,
name: data.name,
parseId: data.parentId,
});
if (data.children) {
data.children.forEach((itemChild) => {
loop(itemChild);
});
}
};
loop(item);
});
return res;
};
console.log(treeToArr(tree));
四、 注意事項:深拷貝的必要性
在處理這些轉(zhuǎn)換時,由于 JS 的對象是引用類型,直接修改 item.children 會改變原始數(shù)組的內(nèi)容。
- 快捷方案:const cloneArr = JSON.parse(JSON.stringify(arr))。
- 避坑點:如果數(shù)組項中包含 Date 對象、RegExp 或 Function,JSON.parse 會導致數(shù)據(jù)失真,此時應使用其他深拷貝方案。
到此這篇關(guān)于JS 樹與數(shù)組相互轉(zhuǎn)換的幾種方法的文章就介紹到這了,更多相關(guān)JS 樹與數(shù)組相互轉(zhuǎn)換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解JavaScript如何實現(xiàn)一個簡易的Promise對象
Promise對象的作用將異步操作以同步操作的流程表達出來,避免層層嵌套的回調(diào)函數(shù),而且Promise提供了統(tǒng)一的接口,使得控制異步操作更加容易。本文介紹了如何實現(xiàn)一個簡單的Promise對象,需要的可以參考一下2022-11-11
一文詳解JSON.parse和JSON.stringify的用法
Json.stringify()和toString()兩者雖然都可以講目標值轉(zhuǎn)為字符串,但是還是有本質(zhì)區(qū)別的,下面這篇文章主要給大家介紹了關(guān)于JSON.parse和JSON.stringify用法的相關(guān)資料,需要的朋友可以參考下2023-01-01
IE下通過a實現(xiàn)location.href 獲取referer的值
IE下采用window.location.href方式跳轉(zhuǎn)的話,referer值為空在標簽a里面的跳轉(zhuǎn)的話referer就不會空,下面是具體的實現(xiàn)代碼2014-09-09
uniapp開發(fā)小程序?qū)崿F(xiàn)全局懸浮按鈕的代碼
這篇文章主要介紹了uniapp開發(fā)小程序如何實現(xiàn)全局懸浮按鈕,但是在uniapp中式?jīng)]有window對象,和dom元素的,需要獲取頁面上節(jié)點的幾何信息,具體實例代碼詳細跟隨小編一起看看吧2022-03-03

