美文网首页
JS:数组转树形结构的三种方法

JS:数组转树形结构的三种方法

作者: limengzhe | 来源:发表于2022-07-05 22:20 被阅读0次

    我们知道,在前端开发中,并不是每次 API 请求都会返回给我们想要的数据。比如下面这一段数据:

    {
      "city": [
        { "id": 12, "parent_id": 1, "name": "朝阳区" },
        { "id": 241, "parent_id": 24, "name": "田林街道" },
        { "id": 31, "parent_id": 3, "name": "广州市" },
        { "id": 13, "parent_id": 1, "name": "昌平区" },
        { "id": 2421, "parent_id": 242, "name": "上海科技绿洲" },
        { "id": 21, "parent_id": 2, "name": "静安区" },
        { "id": 242, "parent_id": 24, "name": "漕河泾街道" },
        { "id": 22, "parent_id": 2, "name": "黄浦区" },
        { "id": 11, "parent_id": 1, "name": "顺义区" },
        { "id": 2, "parent_id": 0, "name": "上海市" },
        { "id": 24, "parent_id": 2, "name": "徐汇区" },
        { "id": 1, "parent_id": 0, "name": "北京市" },
        { "id": 2422, "parent_id": 242, "name": "漕河泾开发区" },
        { "id": 32, "parent_id": 3, "name": "深圳市" },
        { "id": 33, "parent_id": 3, "name": "东莞市" },
        { "id": 3, "parent_id": 0, "name": "广东省" }
      ]
    }
    

    在这段数据中,省,市,区,甚至街道这些有明显层级关系的都作为同级数据排列,这种结构就是典型的对前端很不友好的例子。一般情况下,类似这种数据,我们需要将其转换为树形结构才可以正常使用。

    如果恰好你司的后端大佬愿意修改成你想要的结构,那恭喜你。但实际情况是,大多数人并非这么幸运。所谓靠天靠地不如靠自己,下面我就来介绍几种方法,自己动手修改。

    方法一

    我们知道,浅拷贝是拷贝对象的内存地址,只要修改,所有引用都会同步修改。利用这个特点,我们将子节点依次放入父节点,最后将最外层父节点返回即可。

    /**
     * 数组转树形结构
     * @param {array} list 被转换的数组
     * @param {number|string} root 根节点(最外层节点)的 id
     * @return array
     */
    function arrayToTree(list, root) {
      const result = [] // 用于存放结果
      const map = {} // 用于存放 list 下的节点
    
      // 1. 遍历 list,将 list 下的所有节点以 id 作为索引存入 map
      for (const item of list) {
        map[item.id] = { ...item } // 浅拷贝
      }
    
      // 2. 再次遍历,将根节点放入最外层,子节点放入父节点
      for (const item of list) {
        // 3. 获取节点的 id 和 父 id
        const { id, parent_id } = item // ES6 解构赋值
        // 4. 如果是根节点,存入 result
        if (item.parent_id === root) {
          result.push(map[id])
        } else {
          // 5. 反之,存入到父节点
          map[parent_id].children
            ? map[parent_id].children.push(map[id])
            : (map[parent_id].children = [map[id]])
        }
      }
    
      // 将结果返回
      return result
    }
    

    方法二

    与方法一的原理一样,但只遍历一次。在遍历的过程中,逐渐将子节点和父节点存入到 map。

    /**
     * 数组转树形结构
     * @param {array} list 被转换的数组
     * @param {number|string} root 根节点(最外层节点)
     * @returns array
     */
    function arrayToTreeV2(list, root) {
      const result = [] // 用于存放结果
      const map = {} // 用于存放 list 下的节点
    
      // 遍历 list
      for (const item of list) {
        // 1. 获取节点的 id 和 父 id
        const { id, parent_id } = item // ES6 解构赋值
    
        // 2. 将节点存入 map
        if (!map[id]) map[id] = {}
    
        // 3. 根据 id,将节点与之前存入的子节点合并
        map[id] = map[id].children
          ? { ...item, children: map[id].children }
          : { ...item }
    
        // 4. 如果是根节点,存入 result
        if (parent_id === root) {
          result.push(map[id])
        } else {
          // 5. 反之,存入父节点
          if (!map[parent_id]) map[parent_id] = {}
          if (!map[parent_id].children) map[parent_id].children = []
          map[parent_id].children.push(map[id])
        }
      }
    
      // 将结果返回
      return result
    }
    

    方法三

    方法三利用了递归。每次遍历时,找到将本次遍历的根节点作为父节点的所有子节点,直至找不到有子节点的,此时,filter 返回空数组,递归停止。

    /**
     * 数组转树形结构
     * @param {array} list 被转换的数组
     * @param {number|string} root 根节点(最外层节点)
     * @returns array
     */
    function arrayToTreeV3(list, root) {
      return list
        .filter(item => item.parent_id === root)
        .map(item => ({ ...item, children: arrayToTreeV3(list, item.id) }))
    }
    

    性能对比

    虽然以上三种方法都能实现将数组转换为树形结构,但我们明显看到方法三的代码最少,也最容易理解。方法一和二原理一样,但遍历的次数不一样。写法不一样,意味着性能表现也不一样。

    下面我们就使用 JSBench 测试一下:

    测试结果

    通过结果可以看出,方法一虽然使用了两次遍历,效率却最高。方法三由于使用了递归,代码最少,也便于理解,但效率却很差。

    相关文章

      网友评论

          本文标题:JS:数组转树形结构的三种方法

          本文链接:https://www.haomeiwen.com/subject/kxlebrtx.html