记一次前端目录树递归调用栈溢出的问题

功能描述

事情起因是这样的,公司一个PC单页项目需要实现一个这样的侧边栏:

菜单的数据结构大概是这样的:

js 复制代码
const audience = {
  id: 'audience',
  title: <FormatMessage name="AMCLOUD_AUDIENCE" />,
  type: 'group',
  children: [
    {
      id: 'contacts',
      title: <FormatMessage name="AMCLOUD_CONTACT" />,
      type: 'item',
      url: '/contacts/list',
      icon: icons.ContactsOutlinedIcon,
      breadcrumbs: true
    },
    ...

通过 id 标识目录,多级菜单使用 children 来定义子菜单。

提出问题

问题是,功能需求不会总是展示所有的菜单。在增加了权限系统后,需要判断配置项里 needPermission ,来过滤掉子菜单。一开始业务菜单很少,就写了个递归了维持:

js 复制代码
// menu = [audience, products, settings ...]
function filterMenuItems(menu) {
  return menu.filter(item => {
    if (item.needPermission) {
      return false; // 过滤掉needPermission为true的菜单项
    }
    if (item.children) {
      item.children = filterMenuItems(item.children); // 递归过滤子菜单项
    }
    return true; // 保留其他菜单项
  });
}

上面的代码,通过遍历每一级的目录树,将每一级通过 needPermission 来完成过滤。看起来功能上是没有任何问题的,项目也跑了很长时间,没有出现 bug。

但是问题来了,随着业务量的扩张,children 数量(即子目录)过多时,前端网页总会出现堆栈大小超过最大限额的错误:

上图中,递归深度过大,超出 js 设定的最大值,或者电脑内存占用过多,都会导致上面的错误。为了实现一个更加健壮的程序,避免上面的错误,我们就重构一下这个函数,使用迭代来代替递归。

解决办法

使用迭代来代替递归。

js 复制代码
// tree = [audience, products, settings ...]
function filterTreeData(tree) {
  // 第一级是平铺的多个根,手动过滤一下
  const stack = tree.filter(node => !node.needPermission);
  
  while (stack.length > 0) {
    const node = stack.pop();

    if (node && node.children && node.children.length) {
      // 删除当前节点的子节点中与目标数据相等的节点
      node.children = node.children.filter(child => !child.needPermission);
      
      // 将当前节点的子节点加入栈中,引用关系不变
      stack.push(...node.children);
    }
  }
  
  return tree;
}

上面递归的算法思路是通过额外维护一个栈来实现遍历树的各个子节点并实现过滤。

可以描述如下(假设根节点不会被权限过滤,不然就没有意义了):

  1. 建立空栈
  2. 将树根节点入栈
  3. 从栈尾 pop 一个元素,获取其子元素,将过滤后的子元素重新赋值给当前节点的 children
  4. 按照顺序,将该子元素的值一次入栈
  5. 重复第三步,直到栈空

我上边的代码是该算法的变形,变化处为不止一个根节点,第一级节点需要手动过滤

graph TD Start --> 创建空栈 创建空栈 --> 入栈根节点 入栈根节点 --> 栈尾pop并过滤children 栈尾pop并过滤children --> 将children依次入栈 将children依次入栈 --> 栈空? 将children依次入栈 --> 栈不空? 栈空? --> stop 栈不空? --> 栈尾pop并过滤children

不知道大家还有什么更好的处理目录树的建议呢?可以一起讨论!

相关推荐
摘星编程7 小时前
OpenHarmony + RN:Placeholder文本占位
javascript·react native·react.js
a1117767 小时前
医院挂号预约系统(开源 Fastapi+vue2)
前端·vue.js·python·html5·fastapi
0思必得08 小时前
[Web自动化] Selenium处理iframe和frame
前端·爬虫·python·selenium·自动化·web自动化
行走的陀螺仪9 小时前
uni-app + Vue3编辑页/新增页面给列表页传参
前端·vue.js·uni-app
摘星编程10 小时前
React Native + OpenHarmony:Spinner旋转加载器
javascript·react native·react.js
We་ct10 小时前
LeetCode 205. 同构字符串:解题思路+代码优化全解析
前端·算法·leetcode·typescript
2301_8127314111 小时前
CSS3笔记
前端·笔记·css3
ziblog11 小时前
CSS3白云飘动动画特效
前端·css·css3
越努力越幸运50811 小时前
CSS3学习之网格布局grid
前端·学习·css3
半斤鸡胗11 小时前
css3基础
前端·css