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

201 阅读2分钟

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

1.递归

/*
@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)的方法(推荐)

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.双循环方法

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和一次遍历

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.递归(小批量数据)

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.## 使用堆栈的非递归(大批量数据)

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;
}