使用JavaScript將扁平數(shù)據(jù)轉(zhuǎn)換為樹(shù)形結(jié)構(gòu)的多種實(shí)現(xiàn)方法
一、理解數(shù)據(jù)結(jié)構(gòu)
1. 扁平數(shù)據(jù)結(jié)構(gòu)示例
const flatData = [ { id: 1, name: '部門(mén)1', parentId: 0 }, { id: 2, name: '部門(mén)2', parentId: 1 }, { id: 3, name: '部門(mén)3', parentId: 1 }, { id: 4, name: '部門(mén)4', parentId: 3 }, { id: 5, name: '部門(mén)5', parentId: 3 }, { id: 6, name: '部門(mén)6', parentId: 0 }, ];
2. 期望的樹(shù)形結(jié)構(gòu)
[ { id: 1, name: '部門(mén)1', children: [ { id: 2, name: '部門(mén)2' }, { id: 3, name: '部門(mén)3', children: [ { id: 4, name: '部門(mén)4' }, { id: 5, name: '部門(mén)5' } ] } ] }, { id: 6, name: '部門(mén)6' } ]
二、實(shí)現(xiàn)方法
方法1:遞歸實(shí)現(xiàn)(經(jīng)典算法)
function buildTree(items, parentId = 0) { const result = []; for (const item of items) { if (item.parentId === parentId) { const children = buildTree(items, item.id); if (children.length) { item.children = children; } result.push(item); } } return result; } const treeData = buildTree(flatData); console.log(JSON.stringify(treeData, null, 2));
優(yōu)點(diǎn):
- 邏輯清晰直觀
- 適合理解樹(shù)形結(jié)構(gòu)的構(gòu)建過(guò)程
缺點(diǎn):
- 時(shí)間復(fù)雜度較高(O(n^2))
- 大數(shù)據(jù)量時(shí)性能較差
方法2:使用對(duì)象引用(高效算法)
function buildTreeOptimized(items) { const itemMap = {}; const result = []; // 首先構(gòu)建哈希映射 for (const item of items) { itemMap[item.id] = { ...item, children: [] }; } // 構(gòu)建樹(shù)結(jié)構(gòu) for (const item of items) { const node = itemMap[item.id]; if (item.parentId === 0) { result.push(node); } else { if (itemMap[item.parentId]) { itemMap[item.parentId].children.push(node); } } } return result; } const optimizedTree = buildTreeOptimized(flatData); console.log(JSON.stringify(optimizedTree, null, 2));
優(yōu)點(diǎn):
- 時(shí)間復(fù)雜度O(n),性能優(yōu)異
- 適合大數(shù)據(jù)量處理
缺點(diǎn):
- 需要額外的內(nèi)存空間存儲(chǔ)映射
方法3:使用Map數(shù)據(jù)結(jié)構(gòu)(ES6+)
function buildTreeWithMap(items) { const map = new Map(); const result = []; // 初始化所有節(jié)點(diǎn)并存入Map items.forEach(item => { map.set(item.id, { ...item, children: [] }); }); // 構(gòu)建樹(shù)結(jié)構(gòu) items.forEach(item => { const node = map.get(item.id); if (item.parentId === 0) { result.push(node); } else { const parent = map.get(item.parentId); if (parent) { parent.children.push(node); } } }); return result; } const mapTree = buildTreeWithMap(flatData); console.log(JSON.stringify(mapTree, null, 2));
優(yōu)點(diǎn):
- 使用Map更現(xiàn)代,性能更好
- 支持任意類型作為鍵
方法4:使用reduce實(shí)現(xiàn)(函數(shù)式編程)
function buildTreeWithReduce(items) { const itemMap = items.reduce((map, item) => { map[item.id] = { ...item, children: [] }; return map; }, {}); return items.reduce((result, item) => { if (item.parentId === 0) { result.push(itemMap[item.id]); } else if (itemMap[item.parentId]) { itemMap[item.parentId].children.push(itemMap[item.id]); } return result; }, []); } const reducedTree = buildTreeWithReduce(flatData); console.log(JSON.stringify(reducedTree, null, 2));
優(yōu)點(diǎn):
- 函數(shù)式風(fēng)格,代碼簡(jiǎn)潔
- 兩次遍歷完成構(gòu)建
三、進(jìn)階功能實(shí)現(xiàn)
1. 添加排序功能
function buildTreeWithSort(items, sortBy = 'id') { const itemMap = {}; const result = []; // 構(gòu)建映射 items.forEach(item => { itemMap[item.id] = { ...item, children: [] }; }); // 構(gòu)建樹(shù)結(jié)構(gòu) items.forEach(item => { const node = itemMap[item.id]; if (item.parentId === 0) { result.push(node); } else if (itemMap[item.parentId]) { itemMap[item.parentId].children.push(node); } }); // 遞歸排序 function sortTree(nodes) { nodes.sort((a, b) => a[sortBy] - b[sortBy]); nodes.forEach(node => { if (node.children.length) { sortTree(node.children); } }); } sortTree(result); return result; } const sortedTree = buildTreeWithSort(flatData, 'id'); console.log(JSON.stringify(sortedTree, null, 2));
2. 處理循環(huán)引用
function buildTreeWithCycleDetection(items) { const itemMap = {}; const result = []; const visited = new Set(); // 構(gòu)建映射 items.forEach(item => { itemMap[item.id] = { ...item, children: [] }; }); // 檢測(cè)循環(huán)引用 function hasCycle(node, parentIds = new Set()) { if (parentIds.has(node.id)) return true; parentIds.add(node.id); for (const child of node.children) { if (hasCycle(child, new Set(parentIds))) { return true; } } return false; } // 構(gòu)建樹(shù)結(jié)構(gòu) items.forEach(item => { if (!visited.has(item.id)) { const node = itemMap[item.id]; if (item.parentId === 0) { result.push(node); } else if (itemMap[item.parentId]) { itemMap[item.parentId].children.push(node); } visited.add(item.id); // 檢查循環(huán)引用 if (hasCycle(node)) { throw new Error(`檢測(cè)到循環(huán)引用,節(jié)點(diǎn)ID: ${node.id}`); } } }); return result; }
3. 支持自定義子節(jié)點(diǎn)字段名
function buildTreeCustomChildren(items, childrenField = 'children') { const itemMap = {}; const result = []; // 構(gòu)建映射 items.forEach(item => { itemMap[item.id] = { ...item, [childrenField]: [] }; }); // 構(gòu)建樹(shù)結(jié)構(gòu) items.forEach(item => { const node = itemMap[item.id]; if (item.parentId === 0) { result.push(node); } else if (itemMap[item.parentId]) { itemMap[item.parentId][childrenField].push(node); } }); return result; }
四、性能優(yōu)化技巧
- 使用Map代替普通對(duì)象:當(dāng)ID為非數(shù)字時(shí)性能更好
- 批量處理數(shù)據(jù):對(duì)于大數(shù)據(jù)量可分批次處理
- 使用Web Worker:對(duì)于極大數(shù)據(jù)集可在后臺(tái)線程處理
- 惰性加載:只構(gòu)建和渲染可見(jiàn)部分的樹(shù)結(jié)構(gòu)
五、實(shí)際應(yīng)用示例
1. 渲染樹(shù)形菜單(React示例)
function TreeMenu({ data }) { return ( <ul> {data.map(node => ( <li key={node.id}> {node.name} {node.children && node.children.length > 0 && ( <TreeMenu data={node.children} /> )} </li> ))} </ul> ); } // 使用 const treeData = buildTreeOptimized(flatData); ReactDOM.render(<TreeMenu data={treeData} />, document.getElementById('root'));
2. 樹(shù)形表格(Vue示例)
<template> <table> <tbody> <tree-row v-for="node in treeData" :key="node.id" :node="node" :level="0" /> </tbody> </table> </template> <script> import { buildTreeWithMap } from './treeUtils'; export default { data() { return { flatData: [...], // 原始扁平數(shù)據(jù) treeData: [] }; }, created() { this.treeData = buildTreeWithMap(this.flatData); }, components: { TreeRow: { props: ['node', 'level'], template: ` <tr :style="{ paddingLeft: level * 20 + 'px' }"> <td>{{ node.name }}</td> <td>{{ node.otherField }}</td> </tr> <tree-row v-for="child in node.children" :key="child.id" :node="child" :level="level + 1" v-if="node.children" /> ` } } }; </script>
六、總結(jié)與最佳實(shí)踐
選擇合適算法:
- 小數(shù)據(jù)量:遞歸算法簡(jiǎn)單直觀
- 大數(shù)據(jù)量:對(duì)象引用或Map實(shí)現(xiàn)性能更好
處理邊界情況:
- 無(wú)效的parentId引用
- 循環(huán)引用檢測(cè)
- 重復(fù)數(shù)據(jù)處理
擴(kuò)展性考慮:
- 支持自定義子節(jié)點(diǎn)字段名
- 添加排序功能
- 支持異步數(shù)據(jù)加載
性能監(jiān)控:
- 對(duì)于超大數(shù)據(jù)集考慮分頁(yè)或虛擬滾動(dòng)
- 使用性能分析工具監(jiān)控構(gòu)建時(shí)間
測(cè)試建議:
// 單元測(cè)試示例 describe('樹(shù)形結(jié)構(gòu)構(gòu)建', () => { it('應(yīng)正確構(gòu)建樹(shù)形結(jié)構(gòu)', () => { const flatData = [...]; const treeData = buildTreeOptimized(flatData); expect(treeData.length).toBe(2); expect(treeData[0].children.length).toBe(2); expect(treeData[0].children[1].children.length).toBe(2); }); it('應(yīng)處理無(wú)效parentId', () => { const invalidData = [...]; const treeData = buildTreeOptimized(invalidData); expect(treeData.length).toBe(1); }); });
通過(guò)本文介紹的各種方法和技巧,您應(yīng)該能夠根據(jù)實(shí)際需求選擇最適合的方式將扁平數(shù)據(jù)轉(zhuǎn)換為樹(shù)形結(jié)構(gòu)。在實(shí)際項(xiàng)目中,推薦使用對(duì)象引用或Map實(shí)現(xiàn)的高效算法,它們?cè)诖髷?shù)據(jù)量下表現(xiàn)優(yōu)異,同時(shí)代碼也相對(duì)清晰可維護(hù)。
以上就是使用JavaScript將扁平數(shù)據(jù)轉(zhuǎn)換為樹(shù)形結(jié)構(gòu)的多種實(shí)現(xiàn)方法的詳細(xì)內(nèi)容,更多關(guān)于JavaScript扁平數(shù)據(jù)轉(zhuǎn)樹(shù)形結(jié)構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
bootstrap paginator分頁(yè)前后臺(tái)用法示例
這篇文章主要為大家詳細(xì)介紹了bootstrap paginator分頁(yè)前后臺(tái)用法示例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06js實(shí)現(xiàn)按座位號(hào)抽獎(jiǎng)
本文主要介紹了js實(shí)現(xiàn)按座位號(hào)抽獎(jiǎng)的示例代碼。具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-04-04微信小程序?qū)W習(xí)筆記之登錄API與獲取用戶信息操作圖文詳解
這篇文章主要介紹了微信小程序?qū)W習(xí)筆記之登錄API與獲取用戶信息操作,結(jié)合實(shí)例形式分析了微信小程序登陸請(qǐng)求及后臺(tái)交互相關(guān)操作技巧,并結(jié)合圖文形式進(jìn)行說(shuō)明,需要的朋友可以參考下2019-03-03移動(dòng)端a標(biāo)簽下載文件重命名(download)不生效解決辦法
在移動(dòng)端使用a標(biāo)簽下載文件時(shí),文件重命名可能不生效,尤其是在APP內(nèi)嵌頁(yè)面中,這通常是因?yàn)榭缬騿?wèn)題導(dǎo)致的,文中將解決辦法介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10使用JS實(shí)現(xiàn)簡(jiǎn)單的圖片切換功能
這篇文章主要為大家詳細(xì)介紹了使用JS實(shí)現(xiàn)簡(jiǎn)單的圖片切換功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07JavaScript開(kāi)發(fā)人員的10個(gè)關(guān)鍵習(xí)慣小結(jié)
還在一味沒(méi)有目的的編寫(xiě)JavaScript代碼嗎?那么你就OUT了!讓我們一起來(lái)看看小編為大家搜羅的JavaScript開(kāi)發(fā)人員應(yīng)該具備的十大關(guān)鍵習(xí)慣吧2014-12-12一文搞懂JavaScript中bind,apply,call的實(shí)現(xiàn)
bind、call和apply都是Function原型鏈上面的方法,因此不管是使用function聲明的函數(shù),還是箭頭函數(shù)都可以直接調(diào)用。本文就帶你看看如何實(shí)現(xiàn)bind、call和apply2022-06-06原生JS實(shí)現(xiàn)京東查看商品點(diǎn)擊放大
這篇文章主要為大家詳細(xì)介紹了原生JS實(shí)現(xiàn)京東查看商品點(diǎn)擊放大,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12