模板编译成真实 DOM 是一条流水线:模板字符串→AST→渲染函数→VNode→真实 DOM。解析器生成结构化AST,优化器标记静态节点,代码生成器产出render函数,patch算法将VNode映射为真实DOM并高效更新。

模板编译成真实 DOM 不是一次性动作,而是一条分工明确、环环相扣的流水线。核心路径是:模板字符串 → AST → 渲染函数 → VNode(虚拟 DOM)→ 真实 DOM。每一步都承担特定职责,缺一不可。
模板解析:把 HTML 字符串切分成结构化树(AST)
Vue 拿到 <div>{{ msg }}<button @click="handle">点我</button></div> 这类模板后,第一步不是执行,而是“读懂它”。解析器用正则和状态机扫描字符串,识别出标签、属性、指令(如 @click)、插值({{ }})等,并组织成一棵抽象语法树(AST)。这棵树是纯 JS 对象,记录了节点类型、标签名、属性列表、子节点、是否含响应式表达式等信息。比如 {{ msg }} 会被标记为 type=2 的表达式节点,msg 被提取为依赖字段。
AST 优化:提前标记静态内容,减少运行时开销
优化阶段不改变结构,只做“标注”。遍历 AST,找出那些永远不变的节点——比如纯文本 "欢迎来到首页"、没有绑定任何响应式数据的 <span>版权 © 2026</span>。这些节点被打上 static: true 和 staticRoot: true 标记。后续 diff 时,Vue 可直接复用对应的真实 DOM 片段,跳过比对和重建,显著提升更新性能。
代码生成:产出可执行的 render 函数
编译器把优化后的 AST 翻译成一段标准 JavaScript 函数代码。这个函数接收组件实例(this)为上下文,调用内部辅助函数(如 _c 创建元素 VNode,_v 创建文本 VNode,_s 转换值为字符串)来构造整棵 VNode 树。例如,上面的模板最终生成类似:
function render() { with(this) { return _c('div', [_v(_s(msg)), _c('button', { on: { click: handle } }, [_v("点我")])]) } }
这个函数就是渲染的“蓝图”,每次需要更新视图时就调用它。
VNode 到真实 DOM:patch 算法驱动最终落地
首次挂载时,render 函数执行,返回一个 VNode 树。Vue 的 patch 算法接手,递归遍历 VNode,逐个创建真实 DOM 元素(document.createElement)、设置属性、插入子节点、绑定事件。当数据变化触发重新 render,会生成新 VNode 树,patch 再次运行,对比新旧两棵树的差异(diff),只更新真正变化的节点及其属性,避免全量重绘。


















