news 2026/3/25 0:19:04

jQuery 递归渲染多级树形菜单

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
jQuery 递归渲染多级树形菜单

树形菜单是前端开发中高频且核心的交互组件,广泛应用于后台管理系统、文件目录导航、权限管理面板等场景。其核心价值在于高效组织层级化数据,通过折叠 / 展开的交互形式,让复杂的层级关系更易读、更易操作。

本文将从「底层原理」到「实战落地」,手把手教你用 jQuery + 原生 JavaScript 实现一个高可维护、高拓展性的树形菜单。不仅会讲解代码实现,还会拆解递归核心逻辑、优化交互体验、规避常见坑点,新手也能理解并灵活复用。

一、需求分析与技术选型

1. 核心需求(精准版)

  • 数据源:从 JSON 文件读取扁平化菜单数据(实际开发中可替换为后端接口)
  • 数据处理:将扁平化数据转化为多层级树形结构(核心难点)
  • 页面渲染:动态生成 DOM,区分「有子菜单」和「无子菜单」的样式 / 交互
  • 交互体验:
    • 父菜单点击:折叠 / 展开子菜单 + 箭头平滑旋转动画
    • 子菜单点击:弹窗展示菜单名称(可替换为业务逻辑,如路由跳转)
  • 样式适配:基础美化 + hover 反馈 + 层级缩进清晰

2. 技术选型(附选型理由)

技术 / 方案选型理由
jQuery简化 AJAX 请求、DOM 选择 / 操作,降低新手学习成本,兼容大部分项目场景
原生 JS实现递归逻辑(数据处理 / 渲染),保证核心逻辑的轻量与灵活
CSS3transition实现箭头旋转动画,提升交互流畅度,无额外 JS 开销
JSON扁平化数据存储,符合后端接口返回的常见格式,贴近真实开发场景

二、实现步骤(深度拆解版)

步骤 1:准备标准化菜单数据源

真实开发中,后端返回的菜单数据多为「扁平化结构」(便于数据库存储和查询),核心字段包含:

  • id:菜单唯一标识(主键)
  • name:菜单显示名称
  • pid:父菜单 ID(顶级菜单pid=0,无父级)

创建tree-menu.json文件(规范命名,避免中文空格):

json

[ {"id":1,"name":"首页管理","pid":0}, {"id":2,"name":"轮播图设置","pid":1}, {"id":3,"name":"公告管理","pid":1}, {"id":4,"name":"用户管理","pid":0}, {"id":5,"name":"普通用户","pid":4}, {"id":6,"name":"管理员账户","pid":4}, {"id":7,"name":"系统设置","pid":0}, {"id":8,"name":"权限配置","pid":7} ]

注意:JSON 格式必须严格(无末尾逗号、引号为双引号),否则会导致 AJAX 请求解析失败。

步骤 2:搭建高可维护的 HTML 结构

HTML 结构遵循「语义化 + 低耦合」原则,仅保留核心容器,样式与逻辑完全分离:

html

预览

<!DOCTYPE html> <html lang="zh-CN"> <!-- 改为中文,符合国内开发场景 --> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>jQuery 树形菜单实战</title> <!-- 引入 jQuery(推荐使用 CDN,避免本地路径问题) --> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.min.js"></script> <style> /* 基础重置 */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: "Microsoft Yahei", sans-serif; /* 适配中文显示 */ } /* 标题样式 */ .tree-title { text-align: center; margin: 20px 0; color: #333; } /* 菜单容器 */ .tree-container { font-size: 16px; /* 调整字号,提升可读性 */ margin: 0 auto; width: 600px; /* 固定宽度,适配PC端;移动端可改为100% */ } /* 菜单项样式 */ .menu-item { margin: 5px 0; } /* 菜单标题行 */ .menu-title { display: flex; align-items: center; gap: 8px; /* 箭头与文字间距,替代margin */ padding: 10px 15px; cursor: pointer; border-radius: 6px; transition: background-color 0.2s ease; /* hover 平滑过渡 */ } .menu-title:hover { background-color: #2e8b57; /* 海绿色,更柔和 */ color: #fff; } /* 箭头图标 */ .arrow-icon { width: 18px; height: 18px; transition: transform 0.3s ease; flex-shrink: 0; /* 防止图标被压缩 */ } /* 子菜单容器 */ .submenu-container { margin-left: 25px; /* 层级缩进,比原5%更精准 */ display: none; /* 默认隐藏 */ } /* 箭头旋转类 */ .arrow-rotate { transform: rotate(180deg); } </style> </head> <body> <h1 class="tree-title">系统管理菜单</h1> <div class="tree-container"></div> <script> // JS 逻辑写在这里 </script> </body> </html>

优化点:

  1. 新增 CSS 重置,避免浏览器默认样式干扰;
  2. 改用gap控制箭头与文字间距,更现代;
  3. 类名语义化(如tree-container替代items),便于维护;
  4. 适配中文显示,调整字号提升可读性。

步骤 3:健壮的 AJAX 数据请求

jQuery 的$.ajax封装了请求逻辑,重点增加「异常处理」和「路径兼容」:

javascript

运行

$(function () { // 封装请求函数,提升复用性 function getMenuData() { $.ajax({ url: './tree-menu.json', // 相对路径,适配不同部署环境 type: 'GET', // 大写更规范 dataType: 'json', timeout: 5000, // 新增超时设置,避免无限等待 success: function(res) { console.log('菜单数据加载成功:', res); // 转换为树形结构 const treeData = flatToTree(res, 0); // 渲染菜单 renderTreeMenu(treeData); }, error: function(xhr, status, error) { // 分类处理错误,便于排查 let errorMsg = ''; if (status === 'timeout') { errorMsg = '请求超时,请检查网络'; } else if (xhr.status === 404) { errorMsg = 'JSON 文件不存在,请检查路径'; } else { errorMsg = `数据加载失败:${error}`; } console.error(errorMsg); // 页面友好提示 $('.tree-container').html(`<div style="color: red; text-align: center;">${errorMsg}</div>`); } }); } // 初始化请求 getMenuData(); });

优化点:

  1. 封装请求函数,便于复用和后续拓展(如加加载动画);
  2. 新增超时设置和分类错误提示,提升健壮性;
  3. 页面友好提示,替代仅控制台输出,提升用户体验;
  4. 函数 / 变量名改为英文(如flatToTree替代mentdata),符合开发规范。

步骤 4:吃透递归核心 —— 扁平化转树形结构

这是树形菜单的底层核心,先理解递归逻辑再写代码:

递归原理
  1. 入口:传入所有扁平化数据 + 顶级菜单pid=0
  2. 遍历:找到所有pid等于当前值的菜单,作为「当前层级菜单」;
  3. 递归:对每个「当前层级菜单」,再次调用函数,传入其id作为新的pid,查找其子菜单;
  4. 终止:当某菜单无对应子菜单时,递归终止,返回空数组。
优化后的递归函数

javascript

运行

/** * 扁平化数据转树形结构 * @param {Array} flatData - 扁平化菜单数据 * @param {Number} parentId - 父菜单ID * @returns {Array} 树形结构数据 */ function flatToTree(flatData, parentId) { // 过滤 + 映射,替代for-in循环,更简洁高效 return flatData.filter(item => item.pid === parentId).map(item => { // 递归查找子菜单,挂载到children属性(语义化) return { ...item, // 解构原属性 children: flatToTree(flatData, item.id) }; }); }

优化点:

  1. filter + map替代for-in循环,代码更简洁、性能更优;
  2. 添加 JSDoc 注释,提升代码可读性和可维护性;
  3. 属性名改为children(行业通用),替代child
  4. 解构赋值保留原属性,避免直接修改原数据。

步骤 5:递归渲染 DOM(高性能版)

渲染逻辑同样用递归,但优化字符串拼接方式,减少 DOM 操作次数:

javascript

运行

/** * 渲染树形菜单 * @param {Array} treeData - 树形结构数据 * @returns {String} 拼接好的HTML字符串 */ function renderTreeHtml(treeData) { let html = ''; treeData.forEach(item => { html += `<div class="menu-item">`; // 判断是否有子菜单 if (item.children && item.children.length > 0) { // 有子菜单:带箭头,绑定点击事件 html += ` <div class="menu-title" data-id="${item.id}"> <img class="arrow-icon" src="./arrow-down.png" alt="展开/收起"> <span>${item.name}</span> </div> <div class="submenu-container"> ${renderTreeHtml(item.children)} </div> `; } else { // 无子菜单:无箭头,绑定点击事件 html += ` <div class="menu-title" data-id="${item.id}"> <span>${item.name}</span> </div> `; } html += `</div>`; }); return html; } // 渲染菜单到页面 function renderTreeMenu(treeData) { const menuHtml = renderTreeHtml(treeData); $('.tree-container').html(menuHtml); // 绑定点击事件(事件委托,避免动态DOM绑定失效) bindMenuEvent(); }

优化点:

  1. 拆分渲染函数为renderTreeHtml(拼接 HTML)和renderTreeMenu(挂载 DOM),职责单一;
  2. forEach替代for...of,兼容性更好;
  3. 新增data-id属性,便于后续拓展(如获取菜单 ID);
  4. 事件委托绑定(见步骤 6),解决动态 DOM 事件失效问题。

步骤 6:高性能的交互事件绑定

放弃onclick内联事件,改用 jQuery 事件委托,提升性能和可维护性:

javascript

运行

/** * 绑定菜单交互事件(事件委托) */ function bindMenuEvent() { // 父菜单折叠/展开事件 $('.tree-container').on('click', '.menu-title', function() { const $this = $(this); const $submenu = $this.next('.submenu-container'); const $arrow = $this.find('.arrow-icon'); // 仅当有子菜单时,执行折叠/展开 + 箭头旋转 if ($submenu.length > 0) { $submenu.toggle(); $arrow.toggleClass('arrow-rotate'); } else { // 无子菜单:获取菜单名称和ID,执行业务逻辑 const menuName = $this.find('span').text(); const menuId = $this.data('id'); alert(`你点击了菜单:【${menuName}】,ID:${menuId}`); // 实际开发中可替换为:路由跳转、接口请求等 } }); }

核心优化:

  1. 事件委托到静态容器.tree-container,即使动态新增菜单,事件依然有效;
  2. 合并点击事件,区分有无子菜单的逻辑,减少函数数量;
  3. 获取菜单 ID,便于对接后端接口(真实场景必备);
  4. $this缓存 jQuery 对象,避免重复 DOM 查询,提升性能。

三、进阶优化(加分项)

1. 常见问题排查(精准版)

问题现象根因分析解决方案
JSON 加载失败1. 文件路径错误;2. JSON 格式不合法;3. 跨域(本地直接打开 HTML)1. 检查路径是否为相对路径;2. 用 JSON 校验工具(如 JSON.cn)检查格式;3. 启动本地服务(如 Live Server)
菜单不渲染1. 递归函数逻辑错误;2. 数据为空;3. DOM 选择器错误1. 打印treeData检查树形结构;2. 确认 JSON 数据非空;3. 检查类名是否匹配
箭头不旋转1. 图标未找到;2. CSS 类名错误;3. 无箭头元素时执行旋转1. 检查图标路径;2. 核对arrow-rotate类名;3. 加$arrow.length判断
点击事件失效1. 动态 DOM 用了静态事件绑定;2. 事件委托容器错误1. 改用事件委托;2. 确认委托容器是静态 DOM(如.tree-container

2. 功能拓展(企业级场景)

  1. 默认展开指定菜单

javascript

运行

// 渲染后,展开ID为4的菜单(用户管理) function expandSpecifiedMenu(menuId) { const $menu = $(`.menu-title[data-id="${menuId}"]`); $menu.next('.submenu-container').show(); $menu.find('.arrow-icon').addClass('arrow-rotate'); } // 在 renderTreeMenu 中调用 expandSpecifiedMenu(4);
  1. 添加菜单选中状态

css

.menu-title.active { background-color: #1e90ff; color: #fff; }

javascript

运行

// 绑定选中事件 $('.tree-container').on('click', '.menu-title', function() { $(this).addClass('active').siblings('.menu-title').removeClass('active'); // 其他逻辑... });
  1. 防止 XSS 攻击:如果菜单名称来自用户输入,需转义 HTML 特殊字符:

javascript

运行

function escapeHtml(str) { return str.replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#039;'); } // 渲染时调用 <span>${escapeHtml(item.name)}</span>

四、核心总结(深度版)

本文实现的树形菜单,核心价值在于「原理清晰 + 代码可维护 + 贴近实战」,核心要点:

  1. 数据层:用「递归 + filter/map」将扁平化数据转为树形结构,这是所有树形组件的底层逻辑;
  2. 渲染层:递归拼接 HTML 字符串,减少 DOM 操作次数,提升性能;
  3. 交互层:事件委托替代内联事件,解决动态 DOM 事件失效问题,同时降低耦合;
  4. 工程化:语义化命名、JSDoc 注释、错误分类处理,符合企业级开发规范。

这个实现方案不仅能满足基础需求,还能轻松拓展为「带选中状态、默认展开、权限控制」的企业级树形菜单。掌握递归逻辑和事件委托核心,你可以将这套思路迁移到 Vue/React 等框架中,实现跨框架复用。

五、完整代码包(附加价值)

为方便你直接使用,整理了完整的文件结构:

plaintext

tree-menu/ ├── index.html // 核心页面 ├── tree-menu.json // 菜单数据 └── arrow-down.png // 箭头图标(可自行替换)

可直接下载使用,无需修改路径,开箱即用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/14 0:36:11

Kronos金融预测大模型:重塑智能投资决策的新范式

Kronos金融预测大模型&#xff1a;重塑智能投资决策的新范式 【免费下载链接】Kronos Kronos: A Foundation Model for the Language of Financial Markets 项目地址: https://gitcode.com/GitHub_Trending/kronos14/Kronos 在当今瞬息万变的金融市场中&#xff0c;精准…

作者头像 李华
网站建设 2026/3/18 6:32:51

FSMN VAD阿里云部署方案:ECS实例配置推荐规格

FSMN VAD阿里云部署方案&#xff1a;ECS实例配置推荐规格 1. 引言&#xff1a;为什么选择FSMN VAD与阿里云结合部署&#xff1f; 语音活动检测&#xff08;Voice Activity Detection, VAD&#xff09;是语音处理流程中的关键前置环节&#xff0c;广泛应用于会议转录、电话质检…

作者头像 李华
网站建设 2026/3/18 8:54:41

BitTorrent Tracker协议深度解析与多网络环境技术方案

BitTorrent Tracker协议深度解析与多网络环境技术方案 【免费下载链接】trackerslist Updated list of public BitTorrent trackers 项目地址: https://gitcode.com/GitHub_Trending/tr/trackerslist 技术背景与问题分析 在分布式文件共享系统中&#xff0c;BitTorrent…

作者头像 李华
网站建设 2026/3/13 19:37:16

亲测Z-Image-ComfyUI,中文文生图效果惊艳

亲测Z-Image-ComfyUI&#xff0c;中文文生图效果惊艳 最近在尝试多个文生图模型时&#xff0c;偶然接触到阿里新推出的 Z-Image-ComfyUI 镜像&#xff0c;原本只是抱着试试看的心态&#xff0c;结果一跑起来就彻底被圈粉了。生成速度飞快、中文理解精准、汉字渲染清晰&#xf…

作者头像 李华
网站建设 2026/3/13 19:11:15

国产AI框架崛起?YOLOE与PaddlePaddle对比

国产AI框架崛起&#xff1f;YOLOE与PaddlePaddle对比 在人工智能技术快速演进的今天&#xff0c;目标检测领域正经历一场从“封闭式识别”到“开放世界感知”的深刻变革。传统YOLO系列模型虽以高效著称&#xff0c;但在面对未知类别或动态场景时显得力不从心。正是在这一背景下…

作者头像 李华
网站建设 2026/3/14 1:54:23

批量处理太慢?HeyGem性能优化提速秘籍来了

批量处理太慢&#xff1f;HeyGem性能优化提速秘籍来了 你是不是也遇到过这种情况&#xff1a;手头有几十个数字人视频要生成&#xff0c;音频都准备好了&#xff0c;结果一个一个上传、点击、等待&#xff0c;半天都搞不完&#xff1f;等全部跑完一看日志&#xff0c;发现系统…

作者头像 李华