如何用纯JavaScript递归解析文本生成嵌套导航栏
纯JavaScript递归实现嵌套下拉导航栏方案
需求背景
我正在构建网页,希望仅通过引入div和script标签,由JavaScript自动生成导航栏,避免重复复制粘贴。目前基础功能已实现,但在解析嵌套下拉菜单(subdropdowns)时遇到瓶颈,寻求纯JavaScript递归实现的解决方案。
现有资源
文本配置文件(testdata.txt)
"Home" ==> "/"; "About" ==> "/about"; DROPDOWN [format=STD,header="Example Dropdown"] { "Introduction" ==> "/example/Introduction", DROPDOWN [format=STD,header="Sub Dropdown"] { "Overview" ==> "/example/overview", "Another item" ==> "/example/anotheritem" } }
CSS样式
body { margin: 0; background-color: azure; } .topnav { overflow: hidden; background-color: #333; } .topnav a { float: left; display: block; color: #f2f2f2; text-align: center; padding: 14px 16px; text-decoration: none; font-size: 17px; } .active { background-color: #076341; color: white; } .topnav .icon { display: none; } .dropdown { float: left; overflow: hidden; } .dropdown .dropbtn { font-size: 17px; border: none; outline: none; color: white; padding: 14px 16px; background-color: inherit; font-family: inherit; margin: 0; } .dropdown-content { display: none; position: absolute; background-color: #f9f9f9; min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); z-index: 1; } .dropdown-content a { float: none; color: black; padding: 12px 16px; text-decoration: none; display: block; text-align: left; } .topnav a:hover, .dropdown:hover .dropbtn { background-color: #555; color: white; } .dropdown-content a:hover { background-color: #ddd; color: black; } .dropdown:hover .dropdown-content { display: block; } @media screen and (max-width: 600px) { .topnav a:not(:first-child), .dropdown .dropbtn { display: none; } .topnav a.icon { float: right; display: block; } } @media screen and (max-width: 600px) { .topnav.responsive {position: relative;} .topnav.responsive .icon { position: absolute; right: 0; top: 0; } .topnav.responsive a { float: none; display: block; text-align: left; } .topnav.responsive .dropdown {float: none;} .topnav.responsive .dropdown-content {position: relative;} .topnav.responsive .dropdown .dropbtn { display: block; width: 100%; text-align: left; } }
未完成的JavaScript代码
document.body.onload = function() { var request = new XMLHttpRequest(); request.open('GET', '/testdata.txt', true); request.send(null); request.onreadystatechange = function () { if (request.readyState === 4 && request.status === 200) { var type = request.getResponseHeader('Content-Type'); if (type.indexOf("text") !== -1) { // 原代码判断逻辑错误 get_data(request.responseText + ""); } } } } function get_data(file_data) { file_data = file_data.replace(/(?:\r\n|\r|\n)/g, ''); file_data = file_data.replace(/([^"]+)|("[^"]+")/g, function($0, $1, $2) { if ($1) { return $1.replace(/\s/g, ''); } else { return $2; } }); var navdiv = document.createElement("DIV"); navdiv.className = "topnav"; navdiv.id = "topnavmenu"; var pieces1 = file_data.split(";"); // 原代码缺失闭合引号 var isBetween = false; for (var i = 0; i < pieces1.length; i++) { if (pieces1[i].indexOf("DROPDOWN") > -1) { // 待实现的下拉菜单处理逻辑 } else if (pieces1[i].indexOf("==>") > -1 && !isBetween) { var name = pieces1[i].split("==>")[0]; var link = pieces1[i].split("==>")[1]; name = name.substring(name.indexOf("\"") + 1, name.lastIndexOf("\"")); // 原代码多取了末尾引号 link = link.substring(link.indexOf("\"") + 1, link.lastIndexOf("\"")); var link_el = build_link(name, link); if (link === window.location.pathname) { link_el.className += " active"; // 原代码用逗号分隔类名错误 } navdiv.appendChild(link_el); }; } document.getElementById('menucontainer').appendChild(navdiv); } function build_link(name, dest) { var link = document.createElement("A"); link.innerHTML = name; link.href = dest; return link; }
解决方案
可行性确认
需求完全可实现,核心逻辑是将自定义文本解析为树形结构 + 递归遍历结构生成DOM元素,无需依赖任何第三方库。
实现步骤
- 文本解析为结构化数据:编写递归解析函数,将自定义格式的导航文本转换为包含普通链接、下拉菜单(及子下拉)的树形对象,方便后续递归处理。
- 递归生成导航DOM:遍历结构化数据,针对普通链接生成
<a>标签,针对下拉菜单生成对应容器并递归渲染内部子项。
完整实现代码
替换原有的JavaScript代码,以下是可直接运行的完整版本:
document.body.onload = function() { var request = new XMLHttpRequest(); request.open('GET', '/testdata.txt', true); request.send(null); request.onreadystatechange = function () { if (request.readyState === 4 && request.status === 200) { var type = request.getResponseHeader('Content-Type'); if (type.indexOf("text") !== -1) { const navTree = parseNavData(request.responseText); renderNav(navTree); } } } } // 解析导航文本为树形结构 function parseNavData(rawData) { // 预处理:移除换行和引号外的空格 const cleaned = rawData.replace(/(?:\r\n|\r|\n)/g, '') .replace(/([^"]+)|("[^"]+")/g, ($0, $1, $2) => $1 ? $1.replace(/\s/g, '') : $2); // 递归解析块内容 function parseBlock(str) { const items = []; let index = 0; const len = str.length; while (index < len) { // 匹配普通链接项 const linkMatch = str.slice(index).match(/"([^"]+)"==>"([^"]+)"/); if (linkMatch) { items.push({ type: 'link', name: linkMatch[1], url: linkMatch[2] }); index += linkMatch[0].length; // 跳过后续分隔符 while (index < len && (str[index] === ',' || str[index] === ';')) index++; continue; } // 匹配下拉菜单 const dropdownStart = str.slice(index).match(/DROPDOWN\[([^\]]+)\]\{/); if (dropdownStart) { // 提取下拉菜单标题 const configStr = dropdownStart[1]; const headerMatch = configStr.match(/header="([^"]+)"/); const header = headerMatch ? headerMatch[1] : 'Dropdown'; // 匹配闭合大括号(处理嵌套) let bracketCount = 1; let blockEndIndex = index + dropdownStart[0].length; while (blockEndIndex < len && bracketCount > 0) { if (str[blockEndIndex] === '{') bracketCount++; if (str[blockEndIndex] === '}') bracketCount--; blockEndIndex++; } // 递归解析下拉内的子项 const innerStr = str.slice(index + dropdownStart[0].length, blockEndIndex - 1); const children = parseBlock(innerStr); items.push({ type: 'dropdown', header, children }); index = blockEndIndex; // 跳过后续分隔符 while (index < len && (str[index] === ',' || str[index] === ';')) index++; continue; } index++; } return items; } return parseBlock(cleaned); } // 递归渲染导航DOM function renderNav(navTree) { const navdiv = document.createElement("DIV"); navdiv.className = "topnav"; navdiv.id = "topnavmenu"; // 递归渲染单个导航项 function renderItem(item, parentContainer) { if (item.type === 'link') { const linkEl = build_link(item.name, item.url); if (item.url === window.location.pathname) { linkEl.classList.add('active'); // 用classList操作类名更规范 } parentContainer.appendChild(linkEl); } else if (item.type === 'dropdown') { // 创建下拉容器 const dropdownDiv = document.createElement('DIV'); dropdownDiv.className = 'dropdown'; // 创建下拉按钮 const dropBtn = document.createElement('button'); dropBtn.className = 'dropbtn'; dropBtn.textContent = item.header; // 创建下拉内容容器 const dropdownContent = document.createElement('DIV'); dropdownContent.className = 'dropdown-content'; // 递归渲染子项 item.children.forEach(child => renderItem(child, dropdownContent)); dropdownDiv.appendChild(dropBtn); dropdownDiv.appendChild(dropdownContent); parentContainer.appendChild(dropdownDiv); } } navTree.forEach(item => renderItem(item, navdiv)); document.getElementById('menucontainer').appendChild(navdiv); } function build_link(name, dest) { var link = document.createElement("A"); link.textContent = name; // 用textContent避免XSS风险 link.href = dest; return link; }
关键改进点
- 修复原JS代码中的语法错误和逻辑漏洞
- 新增
parseNavData函数,实现自定义文本到树形结构的转换,完美支持多层嵌套下拉 - 新增递归渲染逻辑,自动区分普通链接和下拉菜单,递归处理子项
- 使用
classList和textContent替代不规范的DOM操作,提升代码健壮性和安全性
内容的提问来源于stack exchange,提问作者TheCodingBear
相关产品推荐
相关产品推荐

