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

