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

功能描述

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

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

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼5 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax