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

功能描述

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

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

相关推荐
码事漫谈6 分钟前
AI提效,到底能强到什么程度?
前端·后端
IT_陈寒6 分钟前
React hooks依赖数组这个坑差点把我埋了
前端·人工智能·后端
阿祖zu34 分钟前
内容创作 AI 透明化声明倡议与项目开源
前端·后端·github
lpfasd12339 分钟前
TypeScript + Cloudflare 全家桶部署项目全流程
前端·javascript·typescript
ZC跨境爬虫43 分钟前
极验滑动验证码自动化实战:背景提取、缺口定位与Playwright滑动模拟
前端·爬虫·python·自动化
前端Hardy1 小时前
字节/腾讯内部流出!Claude Code 2026王炸玩法!效率暴涨10倍
前端·javascript·vue.js
糟糕好吃1 小时前
AI 全流程解析(LLM / Token / Context / RAG / Prompt / Tool / Skill / Agent)
前端·后端·设计模式
快手技术1 小时前
快手广告系统全面迈入生成式推荐时代!GR4AD:从Token到Revenue的全链路重构
前端·后端
前端Hardy1 小时前
大厂都在偷偷用的 Cursor Rules 封装!告别重复 Prompt,AI 编程效率翻倍
前端·javascript·面试
kyriewen1 小时前
Vite:比Webpack快100倍的“闪电侠”,原理竟然这么简单?
前端·javascript·vite