js基础-递归 树转列表 列表转树 树的过滤 树的查找

455 阅读15分钟

一、树结构介绍

      tree: [
        {
          id: '1',
          title: '节点1',
          children: [
            { id: '1-1', title: '节点1-1' },
            { id: '1-2', title: '节点1-2' }
          ]
        },
        {
          id: '2',
          title: '节点2',
          children: [{ id: '2-1', title: '节点2-1' }]
        }
      ]
  1. 子树:当前节点具有children属性,并且children的长度大于0
  2. 叶子节点:当前节点没有children或者children的长度为0

二、树结构的遍历方法

树结构的常用场景之一就是遍历,遍历分为广度优先遍历和深度优先遍历。

深度优先遍历是可递归的

  1. 深度优先遍历又分为先序遍历和后续遍历
  2. 二叉树中还有中序遍历,可以是递归,也可以是循环

广度优先遍历是非递归的,通常用循环来实现

1、广度优先和深度优先的区别

  1. 广度优先:访问树的第n+1层前必须先访问完第n层
  2. 深度优先:访问完一颗子树再去访问后面的子树,而访问子树的时候,先访问根再访问根的子树,称为先序遍历;先访问子树再访问根,称为后续遍历

2、广度遍历的实现

      const wide = tree => {
        for (const item of tree) {
          console.log(item.title)
          item.children && tree.push(...item.children)
        }
      }

      wide(tree)

打印结果:第一层元素会在第二层元素之前输出

      节点1
      节点2
      节点1-1
      节点1-2
      节点2-1

while循环的写法

      const wide = tree => {
        let item
        while ((item = tree.shift())) {
          console.log(item.title)
          item.children && tree.push(...item.children)
        }
      }

3、深度优先遍历的实现(递归)

  1. 先序遍历
      const firstOrder = tree => {
        for (const item of tree) {
          console.log(item.title)
          item.children && firstOrder(item.children)
        }
      }
      firstOrder(tree)

打印结果:先访问根再访问根的子树

      节点1
      节点1-1
      节点1-2
      节点2
      节点2-1
  1. 后序遍历
      const lastOrder = tree => {
        for (const item of tree) {
          item.children && lastOrder(item.children)
          console.log(item.title)
        }
      }
      lastOrder(tree)

打印结果:先访问根的子树再访问根

      节点1-1
      节点1-2
      节点1
      节点2-1
      节点2

4、深度优先遍历实现(循环)

  1. 先序遍历:和广度优先循环实现类似,要维护一个队列,不同的是子节点不是追加到队列最后,而是加到队列最前面
      const firstOrder = tree => {
        let item
        while ((item = tree.shift())) {
          console.log(item.title)
          item.children && tree.unshift(...item.children)
        }
      }
      firstOrder(tree)

打印结果:

      节点1
      节点1-1
      节点1-2
      节点2
      节点2-1
  1. 后序遍历:
      const lastOrder = tree => {
        let item,
          i = 0
        while ((item = tree[i])) {
          const childCount = item.children ? item.children.length : 0
          if (!childCount || item.children[childCount - 1] === tree[i - 1]) {
            console.log(item.title)
            i++
          } else {
            tree.splice(i, 0, ...item.children)
          }
        }
      }
      lastOrder(tree)

打印结果:

      节点1-1
      节点1-2
      节点1
      节点2-1
      节点2

三、列表和树结构相互转换

列表结构中一般在节点信息中给出父级id,以此作为依赖将列表转换为树

1、列表转为树

      const list = [
        { id: '1', title: '节点1', parentId: '' },
        { id: '1-1', title: '节点1-1', parentId: '1', flag: false },
        { id: '1-2', title: '节点1-2', parentId: '1' },
        { id: '2', title: '节点2', parentId: '' },
        { id: '2-1', title: '节点2-1', parentId: '2' }
      ]

(1)第一种方式

      const listToTree = list => {
        const rootNode = list.filter(n => n.parentId === '')
        const genChildren = parents =>
          parents.map(parent => {
            const children = list.filter(m => m.parentId === parent.id)
            if (children.length) parent.children = genChildren(children)
            return parent
          })
        return genChildren(rootNode)
      }

(2)第二种方式

      const listToTree = list => {
        const info = list.reduce((acc, cur) => ((acc[cur.id] = cur), (cur.children = []), acc), {})
        return list.filter(item => {
          info[item.parentId] && info[item.parentId].children.push(item)
          return !item.parentId
        })
      }

(3)第三种方式:只用循环一次,效率最高

    const listToTree = (list) => {
      const result = [] // 存放结果集
      const itemMap = {}
      for (const item of list) {
        const { id, parentId } = item
        if (!itemMap[id]) itemMap[id] = { children: [] }
        itemMap[id] = { ...item, children: itemMap[id]['children'] }
        const treeItem = itemMap[id]
        if (parentId === '') {
          result.push(treeItem)
        } else {
          if (!itemMap[parentId]) itemMap[parentId] = { children: [] }
          itemMap[parentId].children.push(treeItem)
        }
      }
      return result
    }

(4)第四种方式

      const listToTree = (list, id = '', arr = []) => {
        for (const item of list) if (item.parentId === id) arr.push(item)
        for (const item of arr) {
          item.children = []
          listToTree(list, item.id, item.children)
          if (!item.children.length) delete item.children
        }
        return arr
      }

2、树转为列表

(1)递归实现

      const treeToList = (tree, result = [], level = 0) => {
        for (const item of tree) {
          result.push(item)
          item.level = level + 1 // 添加层级属性
          item.children?.length && treeToList(item.children, result, level + 1)
        }
        return result
      }

(2)循环实现

      const treeToList = tree => {
        let node,
          result = tree.map(node => ((node.level = 1), node))
        for (const [i, item] of result.entries()) {
          if (!item.children) continue
          const list = item.children.map(
            node => ((node.level = item.level + 1), node)
          )
          result.splice(i + 1, 0, ...list)
        }
        return result
      }

四、树结构过滤

1、过滤掉不符合条件的节点:过滤掉flag为false的节点。函数中最好用将tree深复制再操作,否则会改变原树

      const filterFalseByFlag = tree =>
        tree.filter(item => { // tree需要深复制
          if (item.children?.length)
            item.children = filterFalseByFlag(item.children)
          return item.flag !== false
        })

2、递归为每层数据添加level

      const setLevel = (tree, level = 1) =>
        tree.map((item) => { // tree需要深复制
          item.level = level
          if (item.children?.length) item.children = setLevel(item.children, level + 1)
          return item
        })

递归为每层数据添加parentId

    const setParentId = (tree, id = null) => {
      tree = JSON.parse(JSON.stringify(tree))
      for (const item of tree) {
        item.parentId = id
        if (item.children && item.children.length) item.children = setParentId(item.children, item.id)
      }
      return tree
    }

3、过滤接口数据

接口数据(点击查看详细内容)
      const tree = [
        {
          nodeId: '8d6d70cb44ea4172b4c6a3ab760330ea',
          node: {
            powerId: '8d6d70cb44ea4172b4c6a3ab760330ea',
            parentId: '0',
            orderIndex: 4,
            resourceType: 'MENU',
            resourceName: '项目管理',
            level: null,
            resourceDesc: '',
            resourceUrl: '/projectManage',
            resourceIcon: 'iconfont icon-xiangmu',
            moduleId: 'ea3f471cc286450b8329e86e90fe24dc',
            isDeleted: false,
            createTime: '2020-06-22 18:45:26',
            updateTime: '2020-06-22 18:52:36'
          },
          parentId: '0',
          children: null,
          childLeaf: true
        },
        {
          nodeId: '6088a9af2f514e5090ccbd51110424b5',
          node: {
            powerId: '6088a9af2f514e5090ccbd51110424b5',
            parentId: '0',
            orderIndex: 6,
            resourceType: 'MENU',
            resourceName: '资料汇总',
            level: null,
            resourceDesc: '',
            resourceUrl: '/dataCollection',
            resourceIcon: 'iconfont icon-ziliao',
            moduleId: '5ecf31302dff488f84620d5b816d98ee',
            isDeleted: false,
            createTime: '2020-06-22 18:45:26',
            updateTime: '2020-06-22 18:52:56'
          },
          parentId: '0',
          children: null,
          childLeaf: true
        },
        {
          nodeId: '8621da3397374510a17b59ad9cfdca74',
          node: {
            powerId: '8621da3397374510a17b59ad9cfdca74',
            parentId: '0',
            orderIndex: 9,
            resourceType: 'MENU',
            resourceName: '个人中心',
            level: null,
            resourceDesc: '',
            resourceUrl: '/personalCenter',
            resourceIcon: 'iconfont icon-yonghu1',
            moduleId: '242026239a9e40879915867307364365',
            isDeleted: false,
            createTime: '2020-06-22 18:45:26',
            updateTime: '2020-06-22 18:53:19'
          },
          parentId: '0',
          children: null,
          childLeaf: true
        },
        {
          nodeId: '1b4c695c711640949872510e02752b69',
          node: {
            powerId: '1b4c695c711640949872510e02752b69',
            parentId: '0',
            orderIndex: 52,
            resourceType: 'MENU',
            resourceName: '伦理会议',
            level: null,
            resourceDesc: '',
            resourceUrl: '/ethicalMeeting',
            resourceIcon: '',
            moduleId: '52ad856eb1a84fbbb6f31ba59f93aba4',
            isDeleted: false,
            createTime: '2020-06-22 18:45:26',
            updateTime: '2020-06-22 18:45:26'
          },
          parentId: '0',
          children: [
            {
              nodeId: '79234bc38316477680a555f3db4a00dd',
              node: {
                powerId: '79234bc38316477680a555f3db4a00dd',
                parentId: '1b4c695c711640949872510e02752b69',
                orderIndex: 1,
                resourceType: 'MENU',
                resourceName: '待会审项目',
                level: null,
                resourceDesc: '',
                resourceUrl: "/ethicalMeeting/pendingReviewProject'",
                resourceIcon: '',
                moduleId: '52ad856eb1a84fbbb6f31ba59f93aba4',
                isDeleted: false,
                createTime: '2020-06-22 18:45:26',
                updateTime: '2020-06-22 18:45:26'
              },
              parentId: '1b4c695c711640949872510e02752b69',
              children: null,
              childLeaf: true
            },
            {
              nodeId: '5f38c08e24a64ba0bb9b3ee53f192eb2',
              node: {
                powerId: '5f38c08e24a64ba0bb9b3ee53f192eb2',
                parentId: '1b4c695c711640949872510e02752b69',
                orderIndex: 2,
                resourceType: 'MENU',
                resourceName: '会议记录',
                level: null,
                resourceDesc: '',
                resourceUrl: '/ethicalMeeting/pendingReviewProject',
                resourceIcon: '',
                moduleId: '52ad856eb1a84fbbb6f31ba59f93aba4',
                isDeleted: false,
                createTime: '2020-06-22 18:45:26',
                updateTime: '2020-06-22 18:45:26'
              },
              parentId: '1b4c695c711640949872510e02752b69',
              children: null,
              childLeaf: true
            }
          ],
          childLeaf: false
        },
        {
          nodeId: 'a8a55825d32f43fb8dac59bf3aeef0e3',
          node: {
            powerId: 'a8a55825d32f43fb8dac59bf3aeef0e3',
            parentId: '0',
            orderIndex: 82,
            resourceType: 'MENU',
            resourceName: '用户管理',
            level: null,
            resourceDesc: '',
            resourceUrl: '/userManage',
            resourceIcon: 'iconfont icon-yonghuguanli',
            moduleId: 'dc6804f659974d16bf5a498eceb65a25',
            isDeleted: false,
            createTime: '2020-06-22 18:45:26',
            updateTime: '2020-06-22 18:45:26'
          },
          parentId: '0',
          children: [
            {
              nodeId: 'fdb9c937156148cc88060450933e2d72',
              node: {
                powerId: 'fdb9c937156148cc88060450933e2d72',
                parentId: 'a8a55825d32f43fb8dac59bf3aeef0e3',
                orderIndex: 1,
                resourceType: 'MENU',
                resourceName: '本院用户',
                level: null,
                resourceDesc: '',
                resourceUrl: '/userManage/ourHospitalUser',
                resourceIcon: '',
                moduleId: 'dc6804f659974d16bf5a498eceb65a25',
                isDeleted: false,
                createTime: '2020-06-22 18:45:26',
                updateTime: '2020-06-22 18:45:26'
              },
              parentId: 'a8a55825d32f43fb8dac59bf3aeef0e3',
              children: null,
              childLeaf: true
            }
          ],
          childLeaf: false
        }
      ]

递归处理:(只保留需要的数据)

      const filter = (data, result = []) => {
        result = data.map((item) => {
          if (item.children) item.children = filter(item.children, [])
          return {
            path: item.node.resourceUrl,
            text: item.node.resourceName,
            icon: item.node.resourceIcon,
            children: item.children || []
          }
        })
        return result
      }

效果: image.png

使用reduce处理数据(filter和map的结合)

接口数据(点击查看详细内容)
[
    {
        "path": "/dashboard",
        "component": "Layout",
        "hidden": false,
        "meta": {
            "noCache": false,
            "icon": "icon-home",
            "link": null,
            "title": "首页"
        },
        "name": "/dashboard",
        "id": 1135
    },
    {
        "redirect": "noRedirect",
        "component": "Layout",
        "hidden": false,
        "children": [
            {
                "path": "/org/adminoa",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "OA组织管理"
                },
                "name": "/org/adminoa",
                "id": 1141
            },
            {
                "path": "/org/admin",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "组织管理"
                },
                "name": "/org/admin",
                "id": 1145
            }
        ],
        "meta": {
            "noCache": false,
            "icon": "icon-organization",
            "link": null,
            "title": "组织管理"
        },
        "id": 1136,
        "alwaysShow": true
    },
    {
        "redirect": "noRedirect",
        "component": "Layout",
        "hidden": false,
        "children": [
            {
                "path": "/user/adminoa",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "OA用户管理"
                },
                "name": "/user/adminoa",
                "id": 1155
            },
            {
                "path": "/user/admin",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "用户管理"
                },
                "name": "/user/admin",
                "id": 1156
            }
        ],
        "meta": {
            "noCache": false,
            "icon": "icon-user",
            "link": null,
            "title": "用户管理"
        },
        "id": 1137,
        "alwaysShow": true
    },
    {
        "redirect": "noRedirect",
        "component": "Layout",
        "hidden": false,
        "children": [
            {
                "path": "/sys-admin/account",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "账号管理"
                },
                "name": "/sys-admin/account",
                "id": 1168
            },
            {
                "path": "/sys-admin/role",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "角色管理"
                },
                "name": "/sys-admin/role",
                "id": 1169
            },
            {
                "path": "/sys-admin/menu",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "菜单管理"
                },
                "name": "/sys-admin/menu",
                "id": 1170
            }
        ],
        "meta": {
            "noCache": false,
            "icon": "icon-system",
            "link": null,
            "title": "系统管理"
        },
        "id": 1138,
        "alwaysShow": true
    },
    {
        "redirect": "noRedirect",
        "component": "Layout",
        "hidden": false,
        "children": [
            {
                "path": "/log/loginlog",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "登录日志"
                },
                "name": "/log/loginlog",
                "id": 1221
            },
            {
                "path": "/log/operatelog",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "操作日志"
                },
                "name": "/log/operatelog",
                "id": 1222
            },
            {
                "path": "/log/interfacelog",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "接口日志"
                },
                "name": "/log/interfacelog",
                "id": 1223
            }
        ],
        "meta": {
            "noCache": false,
            "icon": "icon-log",
            "link": null,
            "title": "日志管理"
        },
        "id": 1139,
        "alwaysShow": true
    },
    {
        "path": "/nationalDirectory",
        "component": "Layout",
        "hidden": false,
        "meta": {
            "noCache": false,
            "icon": "icon-address",
            "link": null,
            "title": "全国通讯录"
        },
        "name": "/nationalDirectory",
        "id": 1211
    },
    {
        "redirect": "noRedirect",
        "component": "Layout",
        "hidden": false,
        "children": [
            {
                "path": "/policySettings/passwordSet",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "密码策略"
                },
                "name": "/policySettings/passwordSet",
                "id": 1225
            },
            {
                "path": "/policySettings/loginSet",
                "component": "Layout",
                "hidden": false,
                "meta": {
                    "noCache": false,
                    "icon": "",
                    "link": null,
                    "title": "登录登出策略"
                },
                "name": "/policySettings/loginSet",
                "id": 1226
            }
        ],
        "meta": {
            "noCache": false,
            "icon": "icon-strategy",
            "link": null,
            "title": "策略配置"
        },
        "id": 1224,
        "alwaysShow": true
    },
    {
        "path": "/insertAdmin",
        "component": "Layout",
        "hidden": false,
        "meta": {
            "noCache": false,
            "icon": "icon-join",
            "link": null,
            "title": "接入管理"
        },
        "name": "/insertAdmin",
        "id": 1234
    }
]

递归处理:

  const filterData = data =>
    data.reduce((acc, cur) => {
      let { children, hidden, meta, path, id } = cur
      if (children) children = filterData(children, [])
      if (!hidden) {
        const key = path || id
        const { title: label } = meta
        const icon = meta.icon && <i className={`iconfont ${meta.icon}`} />
        acc.push({ label, key, path, icon, children })
      }
      return acc
    }, [])

4、删除children为null[]的children

      const tree = [
        {
          value: 'zhejiang',
          label: 'Zhejiang',
          children: [
            {
              value: 'hangzhou',
              label: 'Hangzhou',
              children: [{ value: 'xihu', label: 'West Lake' }]
            }
          ]
        },
        {
          value: 'jiangsu',
          label: 'Jiangsu',
          children: [{ value: 'nanjing', label: 'Nanjing', children: [] }]
        },
        { value: 'anhui', label: 'anhui', children: null }
      ]

      const recursionRemoveEmpty = (data) => {
        data = JSON.parse(JSON.stringify(data)).filter((item) => {
          if (item.children) item.children = recursionRemoveEmpty(item.children)
          if (!item.children || (item.children && item.children.length === 0)) delete item.children
          return item
        })
        return data
      }
      console.log(tree)
      console.log(recursionRemoveEmpty(tree))

效果:

image.png

五、树结构查找

1、根据id查找当前节点

      const getNodeById = (tree, id) => {
        for (const item of tree) {
          if (item.id === id) return item
          if (item.children?.length) {
            const node = getNodeById(item.children, id)
            if (node) return node
          }
        }
      }

2、根据id查找节点路径

      const getNodePathById = (tree, id, path = []) => {
        if (!tree) return []
        for (const data of tree) {
          path.push(data.id)
          if (data.id === id) return path
          if (data.children) {
            const findChildren = getNodePathById(data.children, id, path)
            if (findChildren.length) return findChildren
          }
          path.pop()
        }
        return []
      }

      const result = getNodePathById(tree, '2-1')

3、根据id查找当前节点和其所有的父级节点

      const getParentsById = (tree, id) => {
        for (const item of tree) {
          if (item.children?.length) {
            const nodeList = getParentsById(item.children, id)
            if (nodeList) return nodeList.concat(item)
          }
          if (item.id === id) return [item]
        }
      }

4、根据当前节点找到其所有子级节点

      const getNodeById = (tree, id) => {
        for (const item of tree) {
          if (item.id === id) return item
          if (item.children?.length) {
            const node = getNodeById(item.children, id)
            if (node) return node
          }
        }
      }
      const node = getNodeById(tree, '1')
      const childs = getChildsByNode([node])

5、获取树中所有的id集合

      getIdsByTree (tree, result = []) {
        for (const item of tree) {
          item.id && result.push(item.id);
          if (item.children) getIdsByTree(item.children, result);
        }
        return result;
      };

六、尾调用优化

1、什么是尾调用

函数的最后一步是return调用另一个函数

      function a() {
        return b()
      }

注意:以下几种情况不是尾调用

      function a() {
        return b() + 10 // 调用后又赋值
      }
      
      function a() {
        b() // return 了undefined
      }
      
      function a() {
        const bb = b()
        return bb // 调用后还有操作
      }
      function a() {
        return b() || c() // b()不是尾调用,c()是尾调用
      }
      等价于
      function a() {
        const bResult = b()
        if (bResult) return bResult
        return c()
      }

这种的也是尾调用

      function a(num) {
        if (num > 10) {
          return b(10)
        } else {
          return b(num)
        }
        return b()
      }

2、尾调用的优点

  1. 调用栈的形成:如果在a函数的内部调用b函数,那么在a的调用记录上方,会产生一个b的调用记录。当b运行完返回结果到a,b的调用记录才会消失。如果b函数中还调用了c函数,那么在b的调用记录上方还有一个c的调用记录,以此类推,形成调用栈。栈内存遵循先进后出,后进先出的原则,直到栈顶的执行完毕,才会释放之前行程的调用记录的内存。
  2. 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内部函数的调用记录取代外层函数的调用记录就可以,所以尾调用的调用栈是恒定的,堆栈数没有随着函数调用的增加而增多。我们可以将一些不是尾调用的函数改写成尾调用的形式,达到优化调用堆栈的目的。
      const a = () => {
        const i = 1
        const j = 2
        return b(i + j)
      }
      a()

      // 等价于
      const a = () => {
        return b(3)
      }
      a()

      // 等价于
      b(3)

解析:

  1. 上面代码中,如果b函数不是尾调用,函数a就需要保存内部变量i和j的值,b的调用位置等信息。但由于调用函数b后,函数a就结束了,所以执行到最后一步,完全可以删除函数a的调用记录,只保留b(3)的调用记录。
  2. 这就叫做尾调用优化,即只保留内层函数的调用记录。如果所有的函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,内存将大大节省,这就是尾递归优化的意义。

3、尾递归

  1. 函数调用自身,称为递归。如果尾调用自身,称为尾递归
  2. 递归非常耗费内存,因为同时需要保存成百上千个调用记录,很容易发生栈溢出错误(Uncaught RangeError: Maximum call stack size exceeded)。对于尾递归来说,由于只存在一个调用记录,所以永远不会发生栈溢出错误

递归求阶乘改写成尾递归调用,复杂度由O(n)降低为O(1):

      // const factorial = (n) => {
      //   if (n === 1) return 1
      //   return n * factorial(n - 1)
      // }

      // 改成尾调用
      const factorial = (n, total = 1) => {
        if (n === 1) return total
        return factorial(n - 1, n * total)
      }

      console.log(factorial(5)) // 120

尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,阶乘函数factorial需要用到一个中间变量total,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,为什么计算5的阶乘,需要传入两个参数5和1?

可以在factorial函数外提供一个尾调用函数tailCall:

      const factorial = (n) => {
        return tailCall(n)
      }
      
      const tailCall = (n, total = 1) => {
        if (n === 1) return total
        return tailCall(n - 1, n * total)
      }
      console.log(factorial(5))

递归求和函数改写成尾递归:

            console.time('add 加载时间')
            const add = (num) => {
              if (num === 1) return 1
              return num + add(num - 1)
            }
            console.timeEnd('add 加载时间')

            console.time('add2加载时间')
            const add2 = (num, sum) => {
              if (num === 1) return sum + num
              return add2(num - 1, sum + num)
            }
            console.timeEnd('add2加载时间')

            console.log(add(5))
            console.log(add2(5, 0))

如果你觉得这篇文章对你有用,可以看看作者封装的库xtt-utils,里面封装了非常实用的js方法。如果你也是vue开发者,那更好了,除了常用的api,还有大量的基于element-ui组件库二次封装的使用方法和自定义指令等,帮你提升开发效率。不定期更新,欢迎交流~