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

功能描述

事情起因是这样的,公司一个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

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

相关推荐
aa小小1 分钟前
localhost 访问异常排查笔记
前端
格子软件2 分钟前
2026年GEO优化系统源码的分布式状态机深度拆解
java·前端·vue.js·vue·geo
陈随易20 分钟前
Rust、Golang、MoonBit 编译成 WASM,体积和速度差距有多大?
前端·后端·程序员
IT_陈寒23 分钟前
Python多线程的坑,我居然现在才踩到
前端·人工智能·后端
摇滚侠29 分钟前
方法 A 等方法 B 执行完再执行 叫同步调用还是异步调用 JS 默认是同步调用还是异步调用
开发语言·javascript·ecmascript
触底反弹1 小时前
🔥 字符串算法面试三连击:反转、回文、回文变种,搞懂这三题稳了!
前端·javascript·算法
触底反弹1 小时前
AI Tool Use 深度解析:大模型是如何"突破物理限制"调用外部工具的?
javascript·人工智能·后端
竹林8182 小时前
从 RPC 超时到批量签名:我用 @solana/web3.js 重构了一个 NFT 铸造页面,踩了这些坑
前端·javascript
工业HMI实战笔记2 小时前
工业HMI界面布局“1核2辅”黄金结构,适配90%场景
前端·ui·性能优化·自动化·交互
优雅格子衫2 小时前
TypeScript 类的基本使用小结
javascript·ubuntu·typescript