JS 树与数组相互转换的几种方法

 更新时间:2026年02月24日 09:43:14   作者:发现一只大呆瓜  
本文主要介绍了JS 树与数组相互转换的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

在前端业务中,后端返回的扁平化数组(Array)往往需要转换为树形结构(Tree)来适配 UI 组件(如 Element UI 的 Tree 或 Cascader)。掌握多种转换思路及性能差异,是进阶高级前端的必备技能。

一、 核心概念:结构对比

数组结构:每一项通过 parentId 指向父级。

  const nodes = [
    { id: 3, name: '节点C', parentId: 1 },
    { id: 6, name: '节点F', parentId: 3 },
    { id: 0, name: 'root', parentId: null },
    { id: 1, name: '节点A', parentId: 0 },
    { id: 8, name: '节点H', parentId: 4 },
    { id: 4, name: '节点D', parentId: 1 },
    { id: 2, name: '节点B', parentId: 0 },
    { id: 5, name: '节点E', parentId: 2 },
    { id: 7, name: '节点G', parentId: 2 },
    { id: 9, name: '节点I', parentId: 5 },
  ];

树形结构:父级通过 children 数组包裹子级。

  let tree = [
    {
      id: 1,
      name: 'text1',
      parentId: 1,
      children: [
        {
          id: 2,
          name: 'text2',
          parentId: 1,
          children: [
            {
              id: 4,
              name: 'text4',
              parentId: 2,
            },
          ],
        },
        {
          id: 3,
          name: 'text3',
          parentId: 1,
        },
      ],
    },
  ];

二、 数组转树

1. 递归思路

原理

  1. 首先需要传递给函数两个参数:数组、当前的父节点id
  2. 设置一个结果数组res,遍历数组,先找到子元素的父节点id与父节点id一致的子项
  3. 将这个子项的id作为父节点id传入函数,继续遍历
  4. 将遍历的结果作为children返回,并给当前项添加children
  5. 将这个当前项,插入到res里面,并返回

注意:如果不想影响原数组,需要先深拷贝一下数组。const cloneArr = JSON.parse(JSON.stringify (arr))

  const nodes = [
    { id: 3, name: '节点C', parentId: 1 },
    { id: 6, name: '节点F', parentId: 3 },
    { id: 0, name: 'root', parentId: null },
    { id: 1, name: '节点A', parentId: 0 },
    { id: 8, name: '节点H', parentId: 4 },
    { id: 4, name: '节点D', parentId: 1 },
    { id: 2, name: '节点B', parentId: 0 },
    { id: 5, name: '节点E', parentId: 2 },
    { id: 7, name: '节点G', parentId: 2 },
    { id: 9, name: '节点I', parentId: 5 },
  ];
  //递归写法
  const arrToTree1 = (arr, id) => {
    const res = [];
    arr.forEach((item) => {
      if (item.parentId === id) {
        const children = arrToTree1(arr, item.id);
        //如果希望每个元素都有children属性,可以直接赋值
        if (children.length !== 0) {
          item.children = children;
        }
        res.push(item);
      }
    });
    return res;
  };
  console.log(arrToTree1(nodes, null));

2. 非递归思路

原理:利用 filter 进行二次筛选。虽然写法简洁,但在大数据量下性能较差(O(n2)O(n^2)O(n2))。

  1. 函数只需要接受一个参数,也就是需要转换的数组arr
  2. 第一层过滤数组,直接返回一个parentId为根id的元素
  3. 但是在返回之间,需要再根据当前id过滤里面的每一项(过滤规则为如果子项的paentId为当前的id,则在当前项的children插入这个子项)
  const arrToTree2 = (arr) => {
    return arr.filter((father) => {
      const childrenArr = arr.filter((children) => {
        return children.parentId === father.id;
      });
      //如果希望每个元素都有children属性,可以直接赋值
      if (childrenArr.length !== 0) {
        father.children = childrenArr;
      }
      return father.parentId === null;
    });
  };
  console.log(arrToTree2(nodes));

3. Map 对象方案(O(n)O(n)O(n)时间复杂度)

原理:利用对象的引用性质。先将数组转为 Map,再遍历一次即可完成。这是在大数据量下的首选方案。

  const arrToTree3 = (arr) => {
    const map = {};
    const res = [];

    // 1. 建立映射表
    arr.forEach((item) => {
      map[item.id] = { ...item, children: [] };
    });

    // 2. 组装树结构
    arr.forEach((item) => {
      const node = map[item.id];
      if (item.parentId === null) {
        res.push(node);
      } else {
        if (map[item.parentId]) {
          map[item.parentId].children.push(node);
        }
      }
    });
    return res;
  };
  console.log(arrToTree3(nodes));

三、 树转数组

1. 递归遍历思路

原理:定义一个结果数组,递归遍历树的每一层,将节点信息(排除 children)推入数组。

  1. 首先定义一个结果数组res,遍历传入的树
  2. 直接将当前项的id、name、parentId包装在一个新对象里插入
  3. 判断是否有children属性,如果有则遍历children属性每一项,继续执行2、3步骤
  let tree = [
    {
      id: 1,
      name: 'text1',
      parentId: 1,
      children: [
        {
          id: 2,
          name: 'text2',
          parentId: 1,
          children: [
            {
              id: 4,
              name: 'text4',
              parentId: 2,
            },
          ],
        },
        {
          id: 3,
          name: 'text3',
          parentId: 1,
        },
      ],
    },
  ];
  const treeToArr = (tree) => {
    const res = [];
    tree.forEach((item) => {
      const loop = (data) => {
        res.push({
          id: data.id,
          name: data.name,
          parseId: data.parentId,
        });
        if (data.children) {
          data.children.forEach((itemChild) => {
            loop(itemChild);
          });
        }
      };
      loop(item);
    });
    return res;
  };
  console.log(treeToArr(tree));

四、 注意事项:深拷贝的必要性

在处理这些转换时,由于 JS 的对象是引用类型,直接修改 item.children 会改变原始数组的内容。

  • 快捷方案:const cloneArr = JSON.parse(JSON.stringify(arr))。
  • 避坑点:如果数组项中包含 Date 对象、RegExp 或 Function,JSON.parse 会导致数据失真,此时应使用其他深拷贝方案。

到此这篇关于JS 树与数组相互转换的几种方法的文章就介绍到这了,更多相关JS 树与数组相互转换内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • 详解JavaScript如何实现一个简易的Promise对象

    详解JavaScript如何实现一个简易的Promise对象

    Promise对象的作用将异步操作以同步操作的流程表达出来,避免层层嵌套的回调函数,而且Promise提供了统一的接口,使得控制异步操作更加容易。本文介绍了如何实现一个简单的Promise对象,需要的可以参考一下
    2022-11-11
  • Bootstrap 附加导航(Affix)插件实例详解

    Bootstrap 附加导航(Affix)插件实例详解

    附加导航(Affix)插件允许某个 <div> 固定在页面的某个位置。接下来通过本文给大家介绍Bootstrap 附加导航(Affix)插件实例详解,感兴趣的朋友一起看看吧
    2016-06-06
  • 如何解决IONIC页面底部被遮住无法向上滚动问题

    如何解决IONIC页面底部被遮住无法向上滚动问题

    Ionic 是目前最有潜力的一款 HTML5 手机应用开发框架。在开发过程中我们同样会遇到各种各样奇葩的问题。下面小编给大家带来了有关IONIC页面底部被遮住无法向上滚动问题的解决方案
    2016-09-09
  • 一文详解JSON.parse和JSON.stringify的用法

    一文详解JSON.parse和JSON.stringify的用法

    Json.stringify()和toString()两者虽然都可以讲目标值转为字符串,但是还是有本质区别的,下面这篇文章主要给大家介绍了关于JSON.parse和JSON.stringify用法的相关资料,需要的朋友可以参考下
    2023-01-01
  • 浅谈JS中this在各个场景下的指向

    浅谈JS中this在各个场景下的指向

    这篇文章主要介绍了浅谈JS中this在各个场景下的指向,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-08-08
  • IE下通过a实现location.href 获取referer的值

    IE下通过a实现location.href 获取referer的值

    IE下采用window.location.href方式跳转的话,referer值为空在标签a里面的跳转的话referer就不会空,下面是具体的实现代码
    2014-09-09
  • javaScript中的空值和假值

    javaScript中的空值和假值

    JavaScript 是世界上最流行的编程语言。javaScript中有五种空值和假值,分别为false,null,undefined,“”,0。从广义上来说,这五个值都是对应数据类型的无效值或空值
    2017-12-12
  • uniapp开发小程序实现全局悬浮按钮的代码

    uniapp开发小程序实现全局悬浮按钮的代码

    这篇文章主要介绍了uniapp开发小程序如何实现全局悬浮按钮,但是在uniapp中式没有window对象,和dom元素的,需要获取页面上节点的几何信息,具体实例代码详细跟随小编一起看看吧
    2022-03-03
  • javascript 封装Date日期类实例详解

    javascript 封装Date日期类实例详解

    这篇文章主要介绍了javascript-封装Date日期类的相关资料,需要的朋友可以参考下
    2017-05-05
  • layui使用label标签的方法

    layui使用label标签的方法

    今天小编就为大家分享一篇layui使用label标签的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-09-09

最新评论