
本文详解为何原递归函数无法处理 `
` 等非 `li` 元素,并提供基于 `for...of` 的健壮解决方案,确保所有非 `li` 元素(如 `
`、``、``)被安全展开为其子文本节点,最终输出符合预期的纯净 HTML 片段。
问题根源在于 NodeList.prototype.forEach() 的执行时序与 DOM 树动态变更的冲突。当递归调用 testFn(e) 后立即执行 e.replaceWith(...e.childNodes) 时,e 被从 DOM 中移除,其子节点被插入到原位置——这会修改当前 node.childNodes 的长度和索引顺序。而 forEach() 内部是基于初始快照遍历的,后续迭代仍按原始 NodeList 进行,导致部分节点被跳过(尤其是紧邻被替换节点之后的兄弟节点),造成
等元素未被处理。
使用 for...of 循环可规避该问题,因为它在每次迭代时都重新获取当前 childNodes 的迭代器,能响应实时 DOM 变化;更重要的是,它使控制流更清晰、避免闭包陷阱,便于逻辑调试。
以下是修正后的完整实现:
let html = `
<ol>
<li>
<a href="https://www.php.cn/link/93ac0c50dd620dc7b88e5fe05c70e15b">foo link text</a>;</li>
<li>
<a href="https://www.php.cn/link/93ac0c50dd620dc7b88e5fe05c70e15b">bar link text</a>;</li>
</ol><p>Paragraph text baz and biz text.</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill12116" title="DeepBI DHTML 页面生成器"><img
src="https://img.php.cn/upload/skill/001/246/273/6adaf20d3191a227.jpg" alt="DeepBI DHTML 页面生成器" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill12116" title="DeepBI DHTML 页面生成器" class="overflowclass">DeepBI DHTML 页面生成器</a>
<p class="overflowclass">生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力:
- 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面;
- 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”;
- 提及位于 `apps/console/src/featur</p>
</div>
<a rel="nofollow" href="/xiazai/skill12116" title="DeepBI DHTML 页面生成器" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
<p>Paragraph text.</p>
`;
html = `${html}`;
const parsed = new DOMParser().parseFromString(html, 'text/html');
function flattenNonLiElements(node) {
for (const child of node.childNodes) {
// 先递归处理子节点(深度优先)
flattenNonLiElements(child);
// 仅对元素节点进行判断和替换
if (child.nodeType !== Node.ELEMENT_NODE) continue;
// 仅保留
✅ 关键要点总结:
- ❌ 避免在 DOM 修改过程中使用 forEach() 遍历 childNodes;
- ✅ 优先选用 for...of 或传统 for (let i = 0; i
- ✅ replaceWith(...childNodes) 是安全展开元素内容的标准方式,天然支持混合节点类型(文本、元素、注释);
- ⚠️ 注意:若目标元素(如
)内含嵌套 HTML(如
),它们也会被一并展开——这正是需求所要求的“只留文本”效果;如需保留特定内联标签,需额外白名单逻辑; - ? 建议在真实项目中添加边界检查(如 node && node.childNodes),增强鲁棒性。
该方案简洁、可预测,适用于任意层级 HTML 结构的语义化精简处理。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










