Vue 将模板(Template)编译成渲染函数(Render Function)的过程,是其实现声明式编程和高效更新的核心。这个过程可以清晰地划分为三个主要阶段:解析(Parse)、优化(Optimize) 和 生成(Generate)。下图直观地展示了这一完整的转换链条:
flowchart LR
A[Template<br>模板字符串] --> B[解析<br>Parse]
B --> C[AST<br>抽象语法树]
C --> D[优化<br>Optimize]
D --> E[标记静态节点]
E --> F[生成<br>Generate]
F --> G[Render Function<br>渲染函数]
下面我们详细解读每个阶段的具体工作。
🔍 第一阶段:解析 (Parse) - 从字符串到抽象语法树 (AST)
编译过程的第一步是将模板字符串解析成一种能够描述其结构和内容的 JavaScript 对象,即抽象语法树 (AST)。
-
工作原理:Vue 的 HTML 解析器 是这一过程的主力。它使用一系列正则表达式,从左到右顺序扫描模板字符串。当扫描到不同的内容类型时,会触发相应的回调函数:
start(tag, attrs, unary):遇到开始标签(如<div>)时调用,会创建一个元素类型的 AST 节点,并处理其属性。end():遇到结束标签(如</div>)时调用,标志着该元素内容解析完毕,会回退当前的解析上下文。chars(text):遇到文本内容时调用。如果文本中包含{{ }}插值表达式,文本解析器会进一步将其解析为动态的表达式节点;否则,创建静态文本节点。
-
维护层级关系:解析器使用一个栈 (Stack) 结构来维护标签的嵌套层级关系,从而正确构建出 AST 的父子结构。最终得到的 AST 是一个树形结构对象,每个节点都详细描述了其类型、标签名、属性列表、子节点等信息。
⚡ 第二阶段:优化 (Optimize) - 标记静态节点
生成 AST 后,Vue 会对其进行一次优化遍历,主要目的是识别并标记静态节点。
- 何为静态节点:指的是那些其内容完全不依赖响应式数据的节点,例如纯文本
<span>Hello</span>或没有任何动态绑定的<div class="logo"></div>。 - 优化意义:被标记为静态的节点,在后续的视图更新过程中(即虚拟 DOM 的 diff 算法),可以被直接跳过比对。因为它们的 VNode 永远不会改变,可以直接复用。这对于存在大量静态内容的页面来说,是极大的性能提升。
⚙️ 第三阶段:生成 (Generate) - 从 AST 到渲染函数字符串
这是最后一步,也是最关键的一步,即将优化后的 AST 转换为可执行的渲染函数代码。
-
代码生成:
generate函数会递归地遍历 AST,根据节点的类型和属性,生成对应的创建虚拟 DOM (VNode) 的 JavaScript 代码字符串。例如:- 元素节点会生成调用
_c('div', { attrs: { ... } }, [...])的代码。 - 文本和插值表达式会生成调用
_v("Hello" + _s(message))的代码。 这里的_c,_v,_s等都是 Vue 内部用于创建 VNode 的辅助函数。
- 元素节点会生成调用
-
组装函数:最终,这些代码字符串会被包裹在一个
with(this)语句(Vue 2)或使用新的上下文对象(Vue 3 的_ctx)中,并通过new Function(...)的方式实例化成一个真正的 JavaScript 函数——这就是我们最终得到的render函数。当组件渲染或响应式数据变化时,执行这个render函数就会返回一棵新的 VNode 树。
💡 Vue 2 与 Vue 3 的编译差异
虽然核心流程一致,但 Vue 3 在编译层面做了显著优化:
- 更快的解析器:Vue 3 重写了编译器,解析速度更快。
- Block Tree & Patch Flags:Vue 3 引入了 "Block" 概念和 Patch Flags,在编译时更精确地标记动态节点的类型,使得运行时 diff 算法的效率大幅提升。
- Tree-shaking:编译后的代码更利于 Tree-shaking,未使用的模块(如未用到的指令相关代码)可以被更好地移除,减小打包体积。
希望这份分步详解能帮助你清晰地理解 Vue 模板编译的强大魔法。