使用JavaScript將扁平數(shù)據(jù)轉(zhuǎn)換為樹形結(jié)構(gòu)的多種實(shí)現(xiàn)方法
一、理解數(shù)據(jù)結(jié)構(gòu)
1. 扁平數(shù)據(jù)結(jié)構(gòu)示例
const flatData = [
{ id: 1, name: '部門1', parentId: 0 },
{ id: 2, name: '部門2', parentId: 1 },
{ id: 3, name: '部門3', parentId: 1 },
{ id: 4, name: '部門4', parentId: 3 },
{ id: 5, name: '部門5', parentId: 3 },
{ id: 6, name: '部門6', parentId: 0 },
];
2. 期望的樹形結(jié)構(gòu)
[
{
id: 1,
name: '部門1',
children: [
{
id: 2,
name: '部門2'
},
{
id: 3,
name: '部門3',
children: [
{ id: 4, name: '部門4' },
{ id: 5, name: '部門5' }
]
}
]
},
{
id: 6,
name: '部門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):
- 邏輯清晰直觀
- 適合理解樹形結(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)建樹結(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)建樹結(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)建樹結(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)建樹結(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)建樹結(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é)構(gòu)
五、實(shí)際應(yīng)用示例
1. 渲染樹形菜單(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. 樹形表格(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('樹形結(jié)構(gòu)構(gòu)建', () => {
it('應(yīng)正確構(gòu)建樹形結(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)換為樹形結(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)換為樹形結(jié)構(gòu)的多種實(shí)現(xiàn)方法的詳細(xì)內(nèi)容,更多關(guān)于JavaScript扁平數(shù)據(jù)轉(zhuǎn)樹形結(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-06
js實(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-07
JavaScript開發(fā)人員的10個(gè)關(guān)鍵習(xí)慣小結(jié)
還在一味沒(méi)有目的的編寫JavaScript代碼嗎?那么你就OUT了!讓我們一起來(lái)看看小編為大家搜羅的JavaScript開發(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

