JS树形结构与列表结构之间的相互转换

一.列表结构转换为树形结构

1.递归

ini 复制代码
/*
@param list 需要处理的数组 
@param parentId 父节点ID
*/
function listToTree(list, parentId = null) {
  const tree = [];
  
  for (const item of list) {
    if (item.parentId === parentId) {
      const children = listToTree(list, item.id);
      if (children.length > 0) {
        item.children = children;
      }
      tree.push(item);
    }
  }
  
  return tree;
}

2.哈希表(对象/Map)的方法(推荐)

ini 复制代码
function listToTree(items, idKey = 'id', parentKey = 'parentId', childrenKey = 'children') {
    const map = {};
    const roots = [];
    
    // 第一遍遍历:创建哈希映射
    for (const item of items) {
        map[item[idKey]] = { ...item, [childrenKey]: [] };
    }
    
    // 第二遍遍历:建立父子关系
    for (const item of items) {
        const parentId = item[parentKey];
        if (parentId !== null && parentId !== undefined && map[parentId]) {
            map[parentId][childrenKey].push(map[item[idKey]]);
        } else {
            roots.push(map[item[idKey]]);
        }
    }
    
    return roots;
}

3.双循环方法

ini 复制代码
function listToTree(items, idKey = 'id', parentKey = 'parentId', childrenKey = 'children') {
    const roots = [];
    
    for (const item of items) {
        if (!item[parentKey]) {
            roots.push({ ...item, [childrenKey]: [] });
        } else {
            const parent = items.find(p => p[idKey] === item[parentKey]);
            if (parent) {
                if (!parent[childrenKey]) parent[childrenKey] = [];
                parent[childrenKey].push({ ...item, [childrenKey]: [] });
            }
        }
    }
    
    return roots;
}

4.使用Map和一次遍历

ini 复制代码
function listToTree(items, idKey = 'id', parentKey = 'parentId', childrenKey = 'children') {
    const map = new Map();
    const roots = [];
    
    items.forEach(item => {
        const node = { ...item, [childrenKey]: [] };
        map.set(node[idKey], node);
        
        if (node[parentKey] && map.has(node[parentKey])) {
            map.get(node[parentKey])[childrenKey].push(node);
        } else if (!node[parentKey]) {
            roots.push(node);
        }
    });
    
    return roots;
}
方法 时间复杂度 适合场景 优势 劣势
哈希表(对象)两次遍历 O(n) 大数据量、通用场景 最高效,两次线性遍历 需要额外存储哈希表
Map 一次遍历 O(n) 大数据量、ES6 环境 只需一次遍历,代码更简洁 依赖 Map,旧浏览器需 polyfill
双循环嵌套 O(n²) 小数据量(<1000 条) 实现简单 数据量大时性能急剧下降
递归 / 小数据量(<1000 条) 实现简单 数据量大时性能急剧下降

树形结构转列表

1.递归(小批量数据)

scss 复制代码
function treeToList(tree, list = [], parentId = null) {
  for (const node of tree) {
    const { children, ...rest } = node;
    list.push({ ...rest, parentId });
    
    if (children && children.length > 0) {
      treeToList(children, list, node.id);
    }
  }
  return list;
}

2.## 使用堆栈的非递归(大批量数据)

c 复制代码
function treeToListStack(tree) {
  const list = [];
  const stack = [...tree.map(node => ({ node, parentId: null }))];
  
  while (stack.length > 0) {
    const { node, parentId } = stack.pop();
    const { children, ...rest } = node;
    
    list.push({ ...rest, parentId });
    
    if (children && children.length > 0) {
      stack.push(...children.map(child => ({ 
        node: child, 
        parentId: node.id 
      })).reverse());
    }
  }
  
  return list;
}
相关推荐
全宝几秒前
🖲️一行代码实现鼠标换肤
前端·css·html
小小小小宇25 分钟前
前端模拟一个setTimeout
前端
萌萌哒草头将军29 分钟前
🚀🚀🚀 不要只知道 Vite 了,可以看看 Farm ,Rust 编写的快速且一致的打包工具
前端·vue.js·react.js
芝士加1 小时前
Playwright vs MidScene:自动化工具“双雄”谁更适合你?
前端·javascript
Carlos_sam2 小时前
OpenLayers:封装一个自定义罗盘控件
前端·javascript
前端南玖3 小时前
深入Vue3响应式:手写实现reactive与ref
前端·javascript·vue.js
wordbaby3 小时前
React Router 双重加载器机制:服务端 loader 与客户端 clientLoader 完整解析
前端·react.js
itslife3 小时前
Fiber 架构
前端·react.js
3Katrina3 小时前
妈妈再也不用担心我的课设了---Vibe Coding帮你实现期末课设!
前端·后端·设计
hubber3 小时前
一次 SPA 架构下的性能优化实践
前端