如何基于DOM构建标题层级树?现有实现有误求修正
基于DOM标题构建层级树的问题修正
示例DOM
const dom = new DOMParser().parseFromString( `<!DOCTYPE html> <body> <h1>A</h1> <h2>B1</h2> <h2>C</h2> <h3>D</h3> <h3>E</h3> <h4>F</h4> <h2>B2</h2> </body>`, "text/html" );
注:原示例中
<h4>F</h3>标签闭合错误,已修正为<h4>F</h4>
期望输出(层级目录树)
const output = { A: { tag: "H1", children: [ { B1: { tag: "H2" }, C: { tag: "H2", children: [ { D: { tag: "H3" }, E: { tag: "H3" }, children: [{ F: { tag: "H4" } }], }, ], }, B2: { tag: "H2" }, }, ], }, };
原尝试代码
const headerTagSet = new Set(["h1", "h2", "h3", "h4"]); const headerTags = ["h1", "h2", "h3", "h4"]; function buildTree(nodes) { const tree = {}; let currentNode = tree; let prevLevel = 0; const stack = []; for (const node of nodes) { if (node.tagName && headerTagSet.has(node.tagName.toLowerCase())) { const currentLevel = headerTags.findIndex( (tag) => tag === node.tagName.toLowerCase() ); const isNested = currentLevel > prevLevel; if (isNested) { const newNode = { [node.textContent]: { tag: node.tagName, }, }; stack.push(newNode); if (currentNode.childre) { currentNode.childre.push(newNode); } else { currentNode.children = [newNode]; } prevLevel = currentLevel; currentNode = newNode; } else { currentNode[node.textContent] = { tag: node.tagName }; stack.length = 0; } } } return tree; } buildTree(Array.from(dom.body.childNodes));
问题分析与修正
原代码存在以下核心问题:
- 拼写错误:
currentNode.childre应为currentNode.children - 栈的使用逻辑缺失:仅在层级加深时入栈,但层级回退或同级时未正确回溯到对应父节点
- 同级节点处理错误:同级标题未被放入同一父级的children容器中,而是直接挂载到错误的节点上
- 节点结构不符合预期:未按照示例输出的格式,将同层级标题包裹在同一个对象里
修正后的代码
const headerTagSet = new Set(["h1", "h2", "h3", "h4"]); const headerTags = ["h1", "h2", "h3", "h4"]; function buildTree(nodes) { const tree = {}; // 栈中保存每个层级的父节点和对应层级,格式:{ node: 父节点对象, level: 层级 } const stack = []; for (const node of nodes) { const tagName = node.tagName?.toLowerCase(); if (!tagName || !headerTagSet.has(tagName)) continue; const currentLevel = headerTags.findIndex(tag => tag === tagName); const titleText = node.textContent.trim(); const titleNode = { [titleText]: { tag: node.tagName } }; // 层级回退:弹出栈顶元素,直到找到当前层级的父节点 while (stack.length > 0 && stack[stack.length - 1].level >= currentLevel) { stack.pop(); } // 确定父节点容器 let parent; if (stack.length === 0) { parent = tree; } else { const lastParent = stack[stack.length - 1].node; // 确保父节点有children数组,且最后一项是当前层级的容器对象 if (!lastParent.children) lastParent.children = []; let container = lastParent.children[lastParent.children.length - 1]; if (!container) { container = {}; lastParent.children.push(container); } parent = container; } // 将当前标题节点添加到父容器 Object.assign(parent, titleNode); // 将当前标题的内部对象入栈,作为子节点的父级 const currentTitleObj = titleNode[titleText]; stack.push({ node: currentTitleObj, level: currentLevel }); } return tree; } // 测试调用 const result = buildTree(Array.from(dom.body.childNodes)); console.log(result);
关键修正点说明
- 栈的正确使用:栈中存储每个标题节点的内部对象(包含
tag和children的对象)及其层级,层级回退时弹出栈顶元素,直到找到匹配的父节点 - 同级节点处理:同层级标题会被添加到同一个父级
children数组的容器对象中,完全匹配示例输出结构 - 拼写错误修复:修正了
childre的拼写错误 - 节点结构对齐:确保每个标题的子节点都放在自身的
children数组中,同层级标题共享一个容器对象
内容的提问来源于stack exchange,提问作者Joji
相关产品推荐
相关产品推荐

