JavaScript 實現(xiàn)普通數(shù)組數(shù)據(jù)轉(zhuǎn)化為樹形數(shù)據(jù)結(jié)構(gòu)的步驟說明
在 JavaScript 中,將普通數(shù)組數(shù)據(jù)轉(zhuǎn)化為樹形結(jié)構(gòu)的數(shù)據(jù)是一個常見的任務(wù),特別是在處理層級數(shù)據(jù)(例如分類、組織結(jié)構(gòu)等)時。下面是一個詳細(xì)的步驟說明,展示如何將一個扁平的數(shù)組轉(zhuǎn)化為樹形數(shù)據(jù)結(jié)構(gòu)。
示例數(shù)據(jù)
假設(shè)我們有以下的扁平數(shù)組,每個元素代表一個節(jié)點,并且每個節(jié)點包含一個 id
和一個 parentId
,parentId
用于表示父節(jié)點的關(guān)系:
const data = [ { id: 1, name: 'Root', parentId: null }, { id: 2, name: 'Child 1', parentId: 1 }, { id: 3, name: 'Child 2', parentId: 1 }, { id: 4, name: 'Grandchild 1', parentId: 2 }, { id: 5, name: 'Grandchild 2', parentId: 2 }, ];
目標(biāo)
將上述數(shù)據(jù)轉(zhuǎn)化為以下樹形結(jié)構(gòu):
[ { id: 1, name: 'Root', children: [ { id: 2, name: 'Child 1', children: [ { id: 4, name: 'Grandchild 1', children: [] }, { id: 5, name: 'Grandchild 2', children: [] } ] }, { id: 3, name: 'Child 2', children: [] } ] } ]
實現(xiàn)步驟
創(chuàng)建一個空對象來存儲每個節(jié)點的引用:
const nodeMap = {};
遍歷數(shù)組,初始化節(jié)點并填充 nodeMap
:
data.forEach(item => { nodeMap[item.id] = { ...item, children: [] }; });
這樣每個節(jié)點都被映射到 nodeMap
中,并且每個節(jié)點都有一個 children
屬性用于存儲子節(jié)點。
遍歷數(shù)組,將每個節(jié)點添加到其父節(jié)點的 children
中:
data.forEach(item => { if (item.parentId) { // 如果節(jié)點有父節(jié)點,將其添加到父節(jié)點的 children 中 const parent = nodeMap[item.parentId]; if (parent) { parent.children.push(nodeMap[item.id]); } } });
提取根節(jié)點:
根節(jié)點是 parentId
為 null
的節(jié)點。我們可以從 nodeMap
中找出這些節(jié)點。
const tree = Object.values(nodeMap).filter(node => node.parentId === null);
完整代碼示例
以下是將上述步驟整合在一起的完整代碼:
const data = [ { id: 1, name: 'Root', parentId: null }, { id: 2, name: 'Child 1', parentId: 1 }, { id: 3, name: 'Child 2', parentId: 1 }, { id: 4, name: 'Grandchild 1', parentId: 2 }, { id: 5, name: 'Grandchild 2', parentId: 2 }, ]; function buildTree(data) { const nodeMap = {}; // 初始化 nodeMap data.forEach(item => { nodeMap[item.id] = { ...item, children: [] }; }); // 構(gòu)建樹形結(jié)構(gòu) data.forEach(item => { if (item.parentId) { const parent = nodeMap[item.parentId]; if (parent) { parent.children.push(nodeMap[item.id]); } } }); // 提取根節(jié)點 return Object.values(nodeMap).filter(node => node.parentId === null); } const tree = buildTree(data); console.log(JSON.stringify(tree, null, 2));
解釋
初始化節(jié)點映射:
nodeMap
用于存儲每個節(jié)點的引用,并初始化每個節(jié)點的 children
為空數(shù)組。
建立父子關(guān)系:
遍歷數(shù)據(jù),找到每個節(jié)點的父節(jié)點,并將當(dāng)前節(jié)點添加到父節(jié)點的 children
中。
提取根節(jié)點:
通過過濾 nodeMap
中 parentId
為 null
的節(jié)點,得到樹的根節(jié)點。
遞歸函數(shù)實現(xiàn)
function buildTree(flatData, parentId = null) { return flatData .filter(node => node.parentId === parentId) .map(node => ({ ...node, children: buildTree(flatData, node.id) })); }
參數(shù)說明
flatData
: 扁平的數(shù)組數(shù)據(jù)。每個元素包含id
和parentId
,用于表示節(jié)點和它們的父節(jié)點關(guān)系。parentId
: 當(dāng)前要處理的父節(jié)點的 ID。默認(rèn)為null
,表示初始調(diào)用時的根節(jié)點。
函數(shù)實現(xiàn)細(xì)節(jié)
過濾節(jié)點:
flatData.filter(node => node.parentId === parentId)
filter
方法會返回flatData
中所有parentId
等于當(dāng)前parentId
的節(jié)點。這意味著我們在尋找所有直接子節(jié)點。- 初次調(diào)用時,
parentId
是null
,所以我們得到的是所有根節(jié)點。
映射節(jié)點:
.map(node => ({ ...node, children: buildTree(flatData, node.id) }))
map
方法對過濾后的每個節(jié)點執(zhí)行回調(diào)函數(shù)。- 回調(diào)函數(shù)的目的是將當(dāng)前節(jié)點的
children
屬性設(shè)置為一個遞歸調(diào)用buildTree
函數(shù)的結(jié)果。
遞歸調(diào)用:
buildTree(flatData, node.id)
- 對于每個節(jié)點,我們再次調(diào)用
buildTree
函數(shù),將當(dāng)前節(jié)點的id
作為新的parentId
。 - 這會找到當(dāng)前節(jié)點的所有子節(jié)點,并將這些子節(jié)點作為當(dāng)前節(jié)點的
children
屬性。
遞歸過程
初始調(diào)用:
buildTree(flatData)
第一次調(diào)用時 parentId
是 null
,這意味著我們查找所有根節(jié)點。
查找子節(jié)點:
對于每一個根節(jié)點,buildTree
會被調(diào)用,查找該節(jié)點的直接子節(jié)點。
逐級遞歸:
對每個子節(jié)點,buildTree
會遞歸調(diào)用自身,繼續(xù)查找該子節(jié)點的子節(jié)點,直到所有節(jié)點的 children
屬性都被填充。
示例
假設(shè)我們有以下扁平數(shù)據(jù):
const data = [ { id: 1, name: 'Root', parentId: null }, { id: 2, name: 'Child 1', parentId: 1 }, { id: 3, name: 'Child 2', parentId: 1 }, { id: 4, name: 'Grandchild 1', parentId: 2 }, { id: 5, name: 'Grandchild 2', parentId: 2 } ];
調(diào)用 buildTree(data)
時:
第一層:
parentId
是 null
,找到 ID 為 1
的根節(jié)點。
第二層:
對于根節(jié)點(ID 為 1
),調(diào)用 buildTree(data, 1)
查找其子節(jié)點,找到 ID 為 2
和 3
的節(jié)點。
第三層:
對于 ID 為 2
的節(jié)點,調(diào)用 buildTree(data, 2)
查找其子節(jié)點,找到 ID 為 4
和 5
的節(jié)點。
第四層:
ID 為 4
和 5
的節(jié)點沒有子節(jié)點,所以遞歸終止,返回空的 children
數(shù)組。
最終得到的樹形數(shù)據(jù)結(jié)構(gòu)如下:
[ { id: 1, name: 'Root', children: [ { id: 2, name: 'Child 1', children: [ { id: 4, name: 'Grandchild 1', children: [] }, { id: 5, name: 'Grandchild 2', children: [] } ] }, { id: 3, name: 'Child 2', children: [] } ] } ]
總結(jié)
這個 buildTree
函數(shù)使用了遞歸的方式來構(gòu)建樹形數(shù)據(jù)結(jié)構(gòu)。通過過濾、映射和遞歸調(diào)用,它逐層構(gòu)建每個節(jié)點的子節(jié)點,直到所有節(jié)點的 children
屬性都被正確填充。這種方法簡潔且高效,適合處理層級數(shù)據(jù)。
到此這篇關(guān)于JavaScript 實現(xiàn)普通數(shù)組數(shù)據(jù)轉(zhuǎn)化為樹形數(shù)據(jù)結(jié)構(gòu)的文章就介紹到這了,更多相關(guān)js普通數(shù)組轉(zhuǎn)化樹形結(jié)構(gòu)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- JavaScript樹形結(jié)構(gòu)數(shù)組處理之遞歸問題
- JS前端二維數(shù)組生成樹形結(jié)構(gòu)示例詳解
- JavaScript數(shù)組扁平轉(zhuǎn)樹形結(jié)構(gòu)數(shù)據(jù)(Tree)的實現(xiàn)
- JS實現(xiàn)樹形結(jié)構(gòu)與數(shù)組結(jié)構(gòu)相互轉(zhuǎn)換并在樹形結(jié)構(gòu)中查找對象
- JavaScript平鋪數(shù)組轉(zhuǎn)樹形結(jié)構(gòu)的實現(xiàn)示例
- 如何將JavaScript將數(shù)組轉(zhuǎn)為樹形結(jié)構(gòu)
相關(guān)文章
基于 Immutable.js 實現(xiàn)撤銷重做功能的實例代碼
這篇文章主要介紹了基于 Immutable.js 實現(xiàn)撤銷重做功能及一些需要注意的地方,需要的朋友可以參考下2018-03-03JavaScript利用Canvas實現(xiàn)粒子動畫倒計時
粒子動畫就是頁面上通過發(fā)射許多微小粒子來表示不規(guī)則模糊物體。本文將利用canvas實現(xiàn)酷炫的粒子動畫倒計時,感興趣的小伙伴可以嘗試一下2022-12-12Javascript的異步函數(shù)和Promise對象你了解嗎
這篇文章主要為大家詳細(xì)介紹了Javascript異步函數(shù)和Promise對象,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03中級前端工程師必須要掌握的27個JavaScript 技巧(干貨總結(jié))
這篇文章主要介紹了中級前端工程師必須要掌握的27個JavaScript 技巧(干貨總結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-09-09談?wù)凧avaScript中super(props)的重要性
今天小編就為大家分享一篇關(guān)于談?wù)凧avaScript中super(props)的重要性,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02JavaScript實現(xiàn)的商品搶購倒計時功能示例
這篇文章主要介紹了JavaScript實現(xiàn)的商品搶購倒計時功能,可實現(xiàn)分秒級別的實時顯示倒計時效果,涉及js日期時間計算與頁面元素動態(tài)操作相關(guān)技巧,需要的朋友可以參考下2017-04-04