js 常见递归方式

js 平铺

js 数组平铺

  1. 递归法
var arr = [1,2,[3,4,5,[6,7,8],9],10,[11,12]];
function fn(arr){
  let arr1 = []
  arr.forEach((val)=>{
    if(val instanceof Array){
      console.log(1)
      arr1 = arr1.concat(fn(val))
    }else{
        arr1.push(val)
    }
  })
  return arr1
}
fn(arr)
  1. reduce
  function fn(arr) {
    return arr.reduce((prev, cur) => {
        return prev.concat(Array.isArray(cur) ? fn(cur) : cur)
    }, [])
  }
  1. ES6 flat(Infinity)
let arr2 = arr.flat(Infinity)

js 数组对象平铺

const json = [
        {
            id: 1,
            title: "课程1",
            children: [
                { id: 4, title: "课程1-1" },
                {
                    id: 5,
                    title: "课程1-2",
                    children: [
                        { id: 6, title: "课程1-2-1" },
                        { id: 7, title: "课程1-2-2" },
                    ],
                },
            ],
        },
        { id: 2, title: "课程2" },
        { id: 3, title: "课程3" },
    ];
    let arr = [];

    function fn(j) {
        j.forEach(item => {
            arr.push(item)
            //循环判断是否有children
            if (item.children) {
            //如果有 递归去除
                fn(item.children)
            }
        })
        // console.log(1)
        console.log('arr',JSON.stringify(arr))
        // debugger
        arr.forEach(item => {
       // 删除children
            delete item.children
        })
        // 
    }
    fn(json)

js 数组对象平铺


const flatData = (data) => {
    return data.reduce((prev, curr) => {
      prev.push(curr);
      if (curr.children &&curr.children.length > 0) {
        prev.push(...flatData(curr.children));
        // 删除children
        delete curr.children
      }
      return prev;
    }, []);
  };
}

// 把树结构数据打平为一维数组
const flatTree = (treeData) => {
  let result = []
  treeData.forEach((item) => {
    // 先克隆一份数据作为第一层级的填充
    let res = JSON.parse(JSON.stringify(item))
    delete res.children
    result.push(res)
    if (item.children && item.children.length > 0) {
      // 如果当前children为数组并且长度大于0,才可进入flatTree()方法
      result = result.concat(flatTree(item.children))
    }
  })
  return result
}


js 递归

简单示例

//必须要有if判断进行出栈,不然会进行死循环
<script>
    function factorial(n) {
        if (n == 1) return n;
        return n * factorial(n - 1)
    }
    console.log(factorial(5)) // 5 * 4 * 3 * 2 * 1 = 120
</script>

js树结构数据的递归

export const getTreeData = (nodes, nodeId,propertiesInp)=>{
  for(var i=0;i<nodes.length;i++){
    let node = nodes[i]
    let {children} = node
    if(node.nodeId === nodeId){
      node.input = {
        ...node.input,
        ...{
          properties: {...propertiesInp}
        }
      }
    }
    if(children && children.length> 0){
      getTreeData(children, nodeId, propertiesInp)
    }
  }
}

js找上级节点

数据结构

 
 let data = [
        {
          "id": 1,
          "label": "猫爷爷",
          "children": [
            {
              "id": 2,
              "label": "猫爸1",
              "children": [
                {
                  "id": 3,
                  "label": "猫崽1-1",
                },
                {
                  "id": 4,
                  "label": "猫崽1-2",
                }
              ],
            },
            {
              "id": 5,
              "label": "猫爸2",
              "children": [
                {
                  "id": 6,        //根据这个id,查找它的所有上级
                  "label": "猫崽2-1", 
                },
                {
                  "id": 7, 
                  "label": "猫崽2-2", 
                },
              ],
            },
          ]
        },
        {
          "id": 7,
          "label": "猪爷爷",
          "children": [
            {
              "id": 8,
              "label": "猪爸1",
              "children": [
                {
                  "id": 9,
                  "label": "猪崽1",
                },
                {
                  "id": 10,
                  "label": "猪崽2",
                }
              ],
            },
          ]
        },
      ]

JS树形结构,通过子节点,查找上级所有父节点


      let target = { "id": 6, "label": "猫崽2-1" }
      let result = []
      this.findParent(data, target, result)  
      console.log(result) // 打印出结果:['猫爷爷', '猫爸2', '猫崽2-1']
//data:要遍历的数据, target:查找目标, result用于装查找结果的数组
findParent(data, target, result) {
      for (let i in data) {
        let item = data[i]
        if (item.id === target.id) {
          //将查找到的目标数据加入结果数组中
          //可根据需求unshift(item.id)或unshift(item)
          result.unshift(item.label)
          return true
        }
        if (item.children && item.children.length > 0) {
          let ok = this.findParent(item.children, target, result)
          if (ok) {
            result.unshift(item.label)
            return true
          }
        }
      }
      //走到这说明没找到目标
      return false
    }

获取树中的所有祖先节点名称

/**
 * 获取树的所有祖先节点指定的key名
 * @param {Array} treeData 树数据
 * @param {String} childrenStr 子节点的键名
 * @param {String} keyStr 获取指定的key名
 * @returns 获取树链路上所有指定的key名数组
 */
const getTreeParents= (treeData, childrenStr = 'children', keyStr = 'label') => {
  const parentKey = []
  treeData.forEach((item) => {
    if (item[childrenStr] && item[childrenStr].length) {
      parentKey.push(item[keyStr])
      const temp = getTreeParents(item[childrenStr], childrenStr, keyStr)
      if (temp.length) {
        parentKey.push(...temp)
      }
    }
  })
  return parentKey
}

const parentKeys = getTreeParents(data);

扩展

据id和pid把json结构 转 树状结构

const jsonData = [
    { id: '0', pid: '-1', name: '666' },
    { id: '4', pid: '1', name: '大家电' },
    { id: '5', pid: '1', name: '生活电器' },
    { id: '1', pid: '0', name: '家用电器' },
    { id: '2', pid: '0', name: '服饰' },
    { id: '3', pid: '0', name: '化妆' },
    { id: '7', pid: '4', name: '空调' },
    { id: '8', pid: '4', name: '冰箱' },
    { id: '9', pid: '4', name: '洗衣机' },
    { id: '10', pid: '4', name: '热水器' },
    { id: '11', pid: '3', name: '面部护理' },
    { id: '12', pid: '3', name: '口腔护理' },
    { id: '13', pid: '2', name: '男装' },
    { id: '14', pid: '2', name: '女装' },
    { id: '15', pid: '7', name: '海尔空调' },
    { id: '16', pid: '7', name: '美的空调' },
    { id: '19', pid: '5', name: '加湿器' },
    { id: '20', pid: '5', name: '电熨斗' },
    { id: '21', pid: '20', name: '电熨斗子项' },
];

/**
 * 根据id和pid把json结构 转 树状结构
 * @param  jsonArr  {json}      json数据
 * @param  idStr  {String}    id的属性名
 * @param  pidStr  {String}    父id的属性名
 * @param  childrenStr  {String}    children的属性名
 * @return  {Array}     数组
 */
const transData = (jsonArr, idStr, pidStr, childrenStr) => {
    // 存放的最终结果树数组
    const result = [];
    const id = idStr;
    const pid = pidStr;
    const children = childrenStr;
    const len = jsonArr.length;

    // 遍历得到以id为键名的对象(建立整棵树的索引)
    const hash = {};
    jsonArr.forEach(item => {
    		 hash[item[id]] = item;
    });

    for (let j = 0; j < len; j++) {
        const jsonArrItem = jsonArr[j];
        const hashItem = hash[jsonArrItem[pid]];
        if (hashItem) {
            // 如果当前项还没有children属性,则添加该属性并设置为空数组
            !hashItem[children] && (hashItem[children] = []);
            hashItem[children].push(jsonArrItem);
        } else {
            result.push(jsonArrItem);
        }
    }
    return result;
};

const jsonDataTree = transData(jsonData, 'id', 'pid', 'children');
console.log(jsonDataTree);


重新组合树结构中的数据 : 一般用于因后端给的数据字段不够完善,类型不正确,需要前端进行处理树数据。

const treeData = [{
    name: '2021资源',
    key: '1',
    isLeaf: false,
    children: [{
        name: '服务器',
        isLeaf: false,
        key: '6',
        children: [
            {
                isLeaf: false,
                name: '172.168.201.109',
                key: '5',
                children: [
                    {
                        isLeaf: true,
                        children: [],
                        name: 'ping丢包率',
                        key: '2',
                    }, {
                        isLeaf: true,
                        children: [],
                        name: 'ping状态',
                        key: '3',
                    },
                ],
            },
            {
                isLeaf: true,
                children: [],
                name: '192.168.3.6',
                key: '7',
            },
        ],
    }],
}];

// 重新组合树数据(根据需要来重组树结构中的属性字段)
const dealTreeData = (treeData) => {
    const data = treeData.map((item) => ({
        ...item,
        // 新增title字段
        title: item.name,
        // 如果children为空数组,则置为null
        children: (item.children && item.children.length)
            ? dealTreeData(item.children)
            : null,
    }));
    return data;
};

console.log(JSON.stringify(dealTreeData(treeData)));
// 打印结果
// [{
//     name: '2021资源',
//     key: '1',
//     isLeaf: false,
//     title: '2021资源',
//     children: [{
//         name: '服务器',
//         isLeaf: false,
//         key: '6',
//         title: '服务器',
//         children: [{
//             isLeaf: false,
//             name: '172.168.201.109',
//             key: '5',
//             title: '172.168.201.109',
//             children: [{
//                 isLeaf: true,
//                 children: null,
//                 name: 'ping丢包率',
//                 key: '2',
//                 title: 'ping丢包率',
//             }, {
//                 isLeaf: true,
//                 children: null,
//                 name: 'ping状态',
//                 key: '3',
//                 title: 'ping状态',
//             }],
//         }, {
//             isLeaf: true,
//             children: null,
//             name: '192.168.3.6',
//             key: '7',
//             title: '192.168.3.6',
//         }],
//     }],
// }];


获取树中叶子节点的总个数(节点中isLeaf为true的节点)

const treeData = {
    name: '2021资源',
    title: '2021资源',
    key: '1',
    isLeaf: false,
    children: [{
        name: '服务器',
        isLeaf: false,
        title: '服务器',
        key: '6',
        children: [
            {
                name: '172.168.201.109',
                isLeaf: false,
                title: '172.168.201.109',
                key: '5',
                children: [
                    {
                        name: 'ping丢包率',
                        isLeaf: true,
                        children: null,
                        title: 'ping丢包率',
                        key: '2',
                    }, {
                        name: 'ping状态',
                        isLeaf: true,
                        children: null,
                        title: 'ping状态',
                        key: '3',
                    },
                ],
            },
            {
                name: '192.168.3.6',
                isLeaf: true,
                children: null,
                title: '192.168.3.6',
                key: '7',
            },
        ],
    }],
};

const getLeafNum = (treeNode) => {
    let leafNum = 0;
    if (!treeNode) {
        return leafNum;
    }
    if (treeNode.children && treeNode.children.length) {
        treeNode.children.forEach((item) => {
            leafNum += getLeafNum(item);
        });
    } else {
        if (treeNode.isLeaf) {
            leafNum++;
        }
    }
    return leafNum;
};
console.log(getLeafNum(treeData)); // 3


根据过滤条件筛选出需要留下节点的树结构数据: 一般用于前端做树的查询功能。

/**
 * 递归过滤节点,但保留原树结构,即符合条件节点的父路径上所有节点不管是否符合条件都保留
 * @param {Node[]} nodes 要过滤的树
 * @param {node => boolean} predicate 过滤条件,符合条件的节点保留(参数为函数,返回值为布尔值)
 * @param {node => boolean} wrapMatchFn 层级条件(参数为函数,返回值为布尔值)
 * @return 过滤后的包含根节点数组
 */
export const filterSearchTree = (nodes, predicate, wrapMatchFn = () => false) => {
  // 如果已经没有节点了,结束递归
  if (!(nodes && nodes.length)) {
    return []
  }
  const newChildren = []
  for (let i = 0; i < nodes.length; i++) {
    const node = nodes[i]
    // 想要截止匹配的那一层(如果有匹配的则不用递归了,直接取下面所有的子节点)
    if (wrapMatchFn(node) && predicate(node)) {
      newChildren.push(node)
      continue
    }
    const subs = filterSearchTree(node.children, predicate, wrapMatchFn)

    // 以下两个条件任何一个成立,当前节点都应该加入到新子节点集中
    // 1. 子孙节点中存在符合条件的,即 subs 数组中有值
    // 2. 自己本身符合条件
    if ((subs && subs.length) || predicate(node)) {
      node.children = subs || []
      newChildren.push(node)
    }
  }
  return newChildren.length ? newChildren : []
}

const treeData = [{
    key: '全部',
    title: '全部',
    isLeaf: false,
    children: [{
        key: '数据库',
        title: '数据库',
        isLeaf: false,
        children: [
            {
                key: 'mysql',
                title: 'mysql',
                isLeaf: false,
                children: [
                    {
                        key: '142',
                        title: '慢查询',
                        isLeaf: true,
                    }, {
                        key: '137',
                        title: 'QPS',
                        isLeaf: true,
                    }, {
                        key: '143',
                        title: '用户列表',
                        isLeaf: true,
                    },
                ],
            },
            {
                key: '166',
                title: 'SQL',
                isLeaf: true,
            },
        ],
    }],
}];

// 要查找的关键字
const searchValue = 'S';

// 筛选到的树结构数据
const newTreeData = filterSearchTree(
    treeData,
    (node) => {
        if (node.title.indexOf(searchValue) !== -1) {
            return true;
        }
        return false;
    },
);
// const newTreeData = filterSearchTree(
//   cloneTreeDatas,
//   (node) => {
//     if (node.title.includes(searchValue)) {
//       return true
//     }
//     return false
//   },
//   (node) => {
//     // 第二层(显示左侧角色组数据)
//     if (node.groupName) {
//       return true
//     }
//     return false
//   }
// )
console.log(JSON.stringify(newTreeData));
// 打印的结果
// [{
//     key: '全部',
//     title: '全部',
//     isLeaf: false,
//     children: [{
//         key: '数据库',
//         title: '数据库',
//         isLeaf: false,
//         children: [{
//             key: 'mysql',
//             title: 'mysql',
//             isLeaf: false,
//             children: [{
//                 key: '137',
//                 title: 'QPS',
//                 isLeaf: true,
//                 children: null,
//             }],
//         }, {
//             key: '166',
//             title: 'SQL',
//             isLeaf: true,
//             children: null,
//         }],
//     }],
// }];


参考链接 : https://blog.csdn.net/helloxiaoliang/article/details/128646399

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值