You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用纯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元素,无需依赖任何第三方库。

实现步骤

  1. 文本解析为结构化数据:编写递归解析函数,将自定义格式的导航文本转换为包含普通链接、下拉菜单(及子下拉)的树形对象,方便后续递归处理。
  2. 递归生成导航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;
}

关键改进点

  1. 修复原JS代码中的语法错误和逻辑漏洞
  2. 新增parseNavData函数,实现自定义文本到树形结构的转换,完美支持多层嵌套下拉
  3. 新增递归渲染逻辑,自动区分普通链接和下拉菜单,递归处理子项
  4. 使用classList和textContent替代不规范的DOM操作,提升代码健壮性和安全性

内容的提问来源于stack exchange,提问作者TheCodingBear

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 19:24:53