不能直接用 document.body.children 递归生成虚拟 DOM,因为真实 DOM 节点含私有属性、空白文本节点、内联事件函数、CSSStyleDeclaration 等,无法直接 diff;应从 HTML 字符串解析或 template.content 克隆入手,提取结构化信息并转换为可比对的 VNode。

为什么不能直接用 document.body.children 递归生成虚拟 DOM
因为拿到的是真实 DOM 节点,不是可 diff 的 VNode。真实节点带大量私有属性(如 offsetTop、getBoundingClientRect),还混着空格换行等 Text 节点,直接转对象会污染结构、丢失语义、无法比对。
常见错误是写个 walk(node) 函数,只取 node.tagName 和 node.textContent,却忽略:
-
node.childNodes包含无意义的空白文本节点,需用node.nodeType === 1过滤 -
onclick这类内联事件在真实 DOM 是函数引用,VNode 中必须转成字符串或绑定描述符(如{ on: { click: handler } }) -
node.style是CSSStyleDeclaration对象,VNode 中应为{ color: 'red', fontSize: '14px' }这样的 plain object -
input、textarea的运行时值(.value)不会出现在 HTML 属性里,硬拷会丢用户输入
从 HTML 字符串出发构造 VNode 更安全
绕过真实 DOM,避免浏览器强制 layout 和 repaint。尤其适合 SSR、静态预处理、WYSIWYG 导出等场景。
推荐路径:
立即学习“前端免费学习笔记(深入)”;
- 用
DOMParser解析 HTML 字符串 → 得到Document对象,不挂载、无副作用 - 手动 walk
doc.body,只提取tagName、attributes、textContent,跳过所有运行时属性 - 把
class映射为className,for映射为htmlFor,style属性解析为对象 - 文本节点统一收进
children数组,不单独建Text类型节点(除非需要保留语义)
示例片段:
把任意文章或文档内容转换成精美的全屏翻页 HTML 演示文件(幻灯片风格)。当用户发来一篇文章、文档链接、Markdown 内容、或粘贴的文本,并且希望生成演示用的 HTML 时触发。支持任意内容来源:iWiki 链接、KM 链接、本地文件路径、直接粘贴的文本。生成的 HTML 文件保存到本地工作区,并通过企微直接发送给用户。触发词示例:帮我把这篇文章做成PPT、转成演示文档、生成HTML幻灯片、做个演示、转成可以演示的格式、article to deck、make a slide。
const parser = new DOMParser(); const doc = parser.parseFromString(htmlStr, 'text/html'); const vnode = walkElement(doc.body); // 自定义 walk,不访问 offsetTop 等
template.content.cloneNode(true) 是唯一能复用的 DOM 快照
如果你非得从已有 DOM 入手(比如编辑器快照),<template></template> 是唯一安全容器。它天然隔离样式、脚本、事件,且 content 返回 DocumentFragment,批量操作不触发重排。
关键点:
- 别用
el.cloneNode(true)—— 会复制已挂载状态,可能触发connectedCallback或样式泄漏 - 克隆后先批量改 ID、设
.value、补label[for],最后再parent.appendChild(fragment) - 避免循环中反复调用
document.getElementById(),提前缓存原始节点引用 - 大规模映射时,
cloneNode只是起点,真正瓶颈在后续状态同步:读写.value、强制 layout(如查offsetHeight)、ID 冲突修复
虚拟 DOM 不等于性能银弹,防阻塞靠的是“延迟+批处理”
虚拟 DOM 本身不加速,它只是把 DOM 操作从“即时执行”变成“延迟+批处理”。真正防阻塞的关键是:不让浏览器在单次 JS 执行中反复进出渲染流水线。
所以要注意:
- 不要在循环里每轮都调用
render()或appendChild()—— 这等于把 100 次重排拆成 100 次 - diff 算法必须保序:
children数组顺序不能乱,key必须唯一且稳定,否则 patch 会误删重建 - 服务端用
jsdom渲染时,别依赖window或document全局对象 —— 它们是模拟的,getComputedStyle返回空对象,offsetTop是 0
最易被忽略的一点:虚拟 DOM 的 diff 成本随树深和宽线性增长,但真实 DOM 的重排成本是指数级的。所以小页面手动操作 DOM 可能更快,而复杂交互必须靠虚拟 DOM 把变更收敛到最小 patch 集合。


















