0

0

如何通过JavaScript实现树形结构菜单?

狼影

狼影

发布时间:2025-09-20 22:35:01

|

717人浏览过

|

来源于php中文网

原创

答案:通过递归算法将层级数据渲染为嵌套HTML,结合CSS控制样式与JavaScript管理展开折叠状态,并利用虚拟化、懒加载和DocumentFragment优化性能。

如何通过javascript实现树形结构菜单?

通过JavaScript实现树形结构菜单,核心在于利用递归算法处理层级数据,并将其动态渲染为嵌套的HTML元素。这通常涉及将一个具有

children
属性的数组结构转换为多层的
<ul><li>
<div>
结构,同时辅以CSS进行样式控制,以及JavaScript来管理展开/折叠等交互状态。


实现一个树形菜单,我们首先需要一个能够清晰表达层级关系的数据结构。最常见且直观的方式是使用一个数组,数组中的每个对象代表一个菜单项,如果该菜单项有子菜单,则会有一个

children
属性,其值也是一个包含子菜单项的数组。

// 示例数据结构
const menuData = [
  {
    id: '1',
    name: '仪表盘',
    link: '#dashboard'
  },
  {
    id: '2',
    name: '用户管理',
    link: '#users',
    children: [
      {
        id: '2-1',
        name: '用户列表',
        link: '#users/list'
      },
      {
        id: '2-2',
        name: '角色权限',
        link: '#users/roles'
      }
    ]
  },
  {
    id: '3',
    name: '产品管理',
    link: '#products',
    children: [
      {
        id: '3-1',
        name: '商品列表',
        link: '#products/list'
      },
      {
        id: '3-2',
        name: '分类管理',
        link: '#products/categories',
        children: [
          {
            id: '3-2-1',
            name: '主分类',
            link: '#products/categories/main'
          },
          {
            id: '3-2-2',
            name: '子分类',
            link: '#products/categories/sub'
          }
        ]
      }
    ]
  }
];

// 核心渲染函数
function createTreeMenu(data, parentElement) {
  const ul = document.createElement('ul');
  ul.classList.add('menu-level'); // 添加层级标识

  data.forEach(item => {
    const li = document.createElement('li');
    li.classList.add('menu-item');
    li.dataset.id = item.id; // 方便后续操作

    const link = document.createElement('a');
    link.href = item.link || '#';
    link.textContent = item.name;
    li.appendChild(link);

    if (item.children && item.children.length > 0) {
      li.classList.add('has-children'); // 标记有子菜单的项
      // 这里可以添加一个展开/折叠的图标或按钮
      const toggleBtn = document.createElement('span');
      toggleBtn.textContent = ' ▶'; // 简单示例
      toggleBtn.classList.add('menu-toggle');
      li.insertBefore(toggleBtn, link.nextSibling); // 放在链接后面

      // 递归调用,处理子菜单
      const subMenu = createTreeMenu(item.children, li);
      subMenu.classList.add('sub-menu'); // 标记子菜单
      li.appendChild(subMenu);
    }
    ul.appendChild(li);
  });

  parentElement.appendChild(ul); // 将生成的菜单结构添加到父元素
  return ul; // 返回当前层级的UL元素
}

// 假设我们有一个ID为 'app' 的容器
const appContainer = document.getElementById('app');
if (appContainer) {
  createTreeMenu(menuData, appContainer);

  // 添加展开/折叠的事件监听器
  appContainer.addEventListener('click', (event) => {
    const target = event.target;
    if (target.classList.contains('menu-toggle')) {
      const parentLi = target.closest('.has-children');
      if (parentLi) {
        parentLi.classList.toggle('expanded'); // 切换展开状态
        // 也可以直接操作子菜单的显示/隐藏
        const subMenu = parentLi.querySelector('.sub-menu');
        if (subMenu) {
          subMenu.style.display = subMenu.style.display === 'none' ? '' : 'none';
          target.textContent = parentLi.classList.contains('expanded') ? ' ▼' : ' ▶';
        }
      }
    }
  });
}

// 简单的CSS来隐藏子菜单,并设置展开状态
/*
#app .sub-menu {
  display: none;
  padding-left: 20px; // 缩进
}

#app .has-children.expanded > .sub-menu {
  display: block;
}

#app .menu-item {
  list-style: none;
  margin: 5px 0;
}

#app .menu-item a {
  text-decoration: none;
  color: #333;
}

#app .menu-toggle {
  cursor: pointer;
  margin-left: 5px;
}
*/

为什么选择递归而非迭代来实现树形菜单?

在处理树形结构时,递归无疑是一种更自然、更符合人类思维习惯的编程范式。树的定义本身就是递归的——一个树节点可以包含零个或多个子树。当你需要遍历或构建这样一个层级结构时,递归函数能够优雅地“下潜”到每一个子层级,处理完当前层级后,再“回溯”到上一层。

用迭代方式实现树形菜单当然也是可能的,通常会借助于栈(Stack)或队列(Queue)来模拟递归调用的过程,手动管理当前处理的节点和其深度。然而,这种方式往往会增加代码的复杂度和维护成本。你需要显式地维护一个数据结构来存储待处理的节点,以及它们的父节点信息,以便在渲染时正确地构建DOM层级。对于初学者来说,这无疑增加了理解难度。

立即学习Java免费学习笔记(深入)”;

递归的优势在于它将复杂问题分解为相同但规模更小的子问题。

createTreeMenu
函数就是这样,它知道如何处理当前层级的菜单项,如果某个菜单项有子菜单,它就“委托”自己去处理那个子菜单,直到没有子菜单为止。这种模式与我们对树结构的直观理解高度契合,使得代码逻辑更加清晰、简洁,也更不容易出错。当然,递归也有其局限性,例如过深的递归可能导致栈溢出(Stack Overflow),但在现代浏览器中,对于常规的菜单深度,这通常不是一个实际问题。

如何处理菜单项的展开与折叠状态?

菜单项的展开与折叠是树形菜单最核心的交互功能之一。实现这一功能,通常有几种思路,而JavaScript驱动的方式最为灵活和强大。

最直接的方法是在HTML结构中,为每个带有子菜单的

<li>
元素添加一个特定的CSS类,例如
has-children
。当用户点击展开/折叠按钮时,我们通过JavaScript来切换这个
<li>
元素上的另一个CSS类,比如
expanded

// 假设父li元素上有一个 .has-children 类
// 点击事件监听器中:
const parentLi = target.closest('.has-children');
if (parentLi) {
  parentLi.classList.toggle('expanded'); // 切换展开状态
  // 根据 expanded 状态改变图标
  target.textContent = parentLi.classList.contains('expanded') ? ' ▼' : ' ▶';
}

然后,在CSS中,我们可以根据这个

expanded
类来控制子菜单的显示与隐藏:

PathFinder
PathFinder

AI驱动的销售漏斗分析工具

下载
.sub-menu {
  display: none; /* 默认隐藏子菜单 */
  overflow: hidden; /* 配合 max-height 实现过渡效果 */
  max-height: 0;
  transition: max-height 0.3s ease-out;
}

.has-children.expanded > .sub-menu {
  display: block; /* 展开时显示 */
  max-height: 500px; /* 足够大的值,或者计算实际高度 */
}

这种方法的好处是,所有的展示逻辑都集中在CSS中,JavaScript只负责切换类名,实现了关注点分离。为了增强用户体验,可以配合CSS的

transition
属性,让展开和折叠过程更加平滑。

除了直接操作DOM元素的类名,更高级的实现会结合数据管理。在现代前端框架(如React、Vue)中,我们会将每个菜单项的

isExpanded
状态存储在组件的内部状态或全局状态管理中。当用户点击时,更新这个状态,框架会自动根据状态变化重新渲染UI,从而达到展开/折叠的效果。这种数据驱动的模式,在处理复杂交互和状态同步时,显得更加健壮和可维护。同时,为了提高可访问性,别忘了为展开/折叠按钮添加
aria-expanded
属性,告知屏幕阅读器当前菜单的展开状态。

优化大型树形菜单的性能有哪些策略?

当树形菜单的数据量非常庞大时,直接一次性渲染所有节点可能会导致严重的性能问题,例如页面加载缓慢、UI卡顿。这时,我们需要采取一些优化策略来提升用户体验。

一个非常有效的策略是虚拟化(Virtualization)或懒加载(Lazy Loading)。这意味着我们只渲染用户当前可见的菜单项,以及少量即将进入视口的菜单项。对于那些超出屏幕范围的菜单项,我们只保留其数据,而不生成实际的DOM元素。当用户滚动或展开菜单时,再动态地创建和销毁DOM节点。这通常需要一个复杂的滚动容器和视口检测机制,但对于拥有成千上万个节点的树来说,性能提升是巨大的。例如,一些UI库提供了虚拟滚动组件,可以直接应用于树形结构。

其次,初始状态的优化也至关重要。默认情况下,可以将所有子菜单都设置为折叠状态。这样,在页面加载时,浏览器只需要渲染顶层菜单项,大大减少了首次渲染的DOM节点数量和计算量。只有当用户主动点击展开某个菜单时,其子菜单才会被渲染出来。

再者,高效的DOM操作是基础。在JavaScript中,频繁地直接操作DOM会触发浏览器的重排(Reflow)和重绘(Repaint),这是非常耗费性能的。如果需要批量添加或修改DOM元素,可以考虑使用

DocumentFragment
。它是一个轻量级的文档片段,你可以将所有要添加的元素先添加到
DocumentFragment
中,最后再将整个
DocumentFragment
一次性添加到实际的DOM树中。这样可以显著减少DOM操作的次数,从而提升性能。

// 使用 DocumentFragment 优化批量 DOM 操作
function createTreeMenuOptimized(data, parentElement) {
  const fragment = document.createDocumentFragment();
  const ul = document.createElement('ul');
  ul.classList.add('menu-level');

  data.forEach(item => {
    // ... 创建 li 和 link 等元素 ...
    // 将 li 添加到 ul
    ul.appendChild(li);
  });

  fragment.appendChild(ul);
  parentElement.appendChild(fragment); // 一次性将所有元素添加到 DOM
  return ul;
}

最后,如果你的菜单数据需要频繁更新或搜索过滤,考虑数据结构本身的优化。确保你的数据获取和处理逻辑是高效的,例如,如果数据来自后端,确保API响应速度快且数据结构合理。对于前端的搜索功能,可以对数据进行索引或使用Trie树等数据结构来加速查找。在现代前端框架中,利用

shouldComponentUpdate
React.memo
等机制,可以避免不必要的组件重新渲染,这也是一种有效的优化手段。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

549

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

30

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

44

2026.01.06

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

443

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

605

2023.08.10

DOM是什么意思
DOM是什么意思

dom的英文全称是documentobjectmodel,表示文件对象模型,是w3c组织推荐的处理可扩展置标语言的标准编程接口;dom是html文档的内存中对象表示,它提供了使用javascript与网页交互的方式。想了解更多的相关内容,可以阅读本专题下面的文章。

4341

2024.08.14

overflow什么意思
overflow什么意思

overflow是一个用于控制元素溢出内容的属性,当元素的内容超出其指定的尺寸时,overflow属性可以决定如何处理这些溢出的内容。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

1860

2024.08.15

css3transition
css3transition

css3transition属性用于指定如何从一个CSS样式过渡到另一个CSS样式,本专题为大家提供transition相关的文章、相关下载和相关课程,大家可以免费体验。

261

2023.06.27

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

37

2026.03.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 0.9万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

CSS教程
CSS教程

共754课时 | 42.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号