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

功能描述

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

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

相关推荐
GIS之路1 小时前
ArcGIS Pro 中的 Notebooks 入门
前端
IT_陈寒3 小时前
React状态管理终极对决:Redux vs Context API谁更胜一筹?
前端·人工智能·后端
Kagol4 小时前
TinyVue 支持 Skills 啦!现在你可以让 AI 使用 TinyVue 组件搭建项目
前端·agent·ai编程
柳杉4 小时前
从零打造 AI 全球趋势监测大屏
前端·javascript·aigc
simple_lau4 小时前
Cursor配置MasterGo MCP:一键读取设计稿生成高还原度前端代码
前端·javascript·vue.js
睡不着先生4 小时前
如何设计一个真正可扩展的表单生成器?
前端·javascript·vue.js
天蓝色的鱼鱼4 小时前
模块化与组件化:90%的前端开发者都没搞懂的本质区别
前端·架构·代码规范
明君879974 小时前
Flutter 如何给图片添加多行文字水印
前端·flutter
进击的尘埃4 小时前
AI 代码审查工具链搭建:用 AST 解析 + LLM 实现自动化 Code Review 的前端工程方案
javascript
juejin_cn4 小时前
[转][译] 从零开始构建 OpenClaw — 第五部分(对话压缩)
javascript