Vue 的模板编译是一个将开发者编写的模板(通常是在 .vue文件或 template选项中的类 HTML 代码)转换为可执行渲染函数的过程。这个过程是 Vue 实现响应式渲染的核心,它确保了用户界面能够高效地响应数据变化。 为了让你能快速把握全局,下图清晰地展示了模板编译的完整流程和最终目的:
flowchart TD
A[模板 Template] --> B[解析阶段<br>Parse]
B --> C[生成AST<br>抽象语法树]
C --> D[优化阶段<br>Optimize]
D --> E[标记静态节点]
E --> F[代码生成阶段<br>Generate]
F --> G[生成渲染函数<br>Render Function]
G --> H[执行渲染函数<br>生成VNode]
H --> I[Patch/Diff<br>更新真实DOM]
下面,我们来详细解读图中的每一个关键阶段。
🔍 解析阶段
编译过程的第一步是解析(Parse)。Vue 的编译器会读取模板字符串,并运用一系列策略(如正则表达式和递归下降解析器)将其转换为一个抽象语法树(AST)。 这棵 AST 是一个用于描述模板结构的 JavaScript 对象树。不同类型的节点(如元素节点、文本节点、插值表达式 {{ }})都有对应的定义 。解析器在解析过程中,会根据节点的种类调用对应的钩子函数来构建 AST 节点。例如,HTML 解析器是主线,当遇到文本时会调用文本解析器,若文本中包含过滤器则会再调用过滤器解析器 。
⚙️ 优化阶段
生成 AST 之后,下一个关键步骤是优化(Optimize)。优化器会遍历整棵 AST 树,其主要任务是标记静态节点和静态根节点。
- 静态节点:是指那些其内容不依赖于组件响应式数据的节点,例如纯文本
<p>Hello World</p>。 - 静态根节点:是指其本身及其所有子节点都是静态的节点。
被标记为静态的节点在后续的视图更新中永远不会改变。因此,在虚拟 DOM 的 patch(diff)过程中,Vue 可以安全地跳过这些静态节点的比对操作,从而显著提升重渲染时的性能 。Vue 3 更进一步,引入了静态提升(Hoist),将静态节点提升到渲染函数之外,避免了在每次渲染时重新创建这些 VNode 的开销 。
⚡️ 代码生成阶段
最后一个核心阶段是代码生成(Generate)。在这个阶段,编译器会遍历优化后的 AST,并将其转换为一个或多个 JavaScript 字符串,这些字符串最终拼接成渲染函数(render function)的字符串形式。 这个渲染函数字符串会通过 new Function()转换为一个可执行的 JavaScript 函数 。当组件渲染时,Vue 执行这个渲染函数。这个函数内部会调用一些 Vue 内部的创建虚拟节点的方法(例如 _c, _v等),最终返回一个描述页面结构的虚拟 DOM 树(VNode)。虚拟 DOM 是对真实 DOM 的轻量级 JavaScript 对象表示。当组件的数据发生变化时,会生成一个新的 VNode 树。Vue 的运行时核心会使用 diff 算法(也称为 patch 过程) 对比新旧 VNode 树,找出差异,然后高效地更新真实 DOM,只应用必要的更改 。
💎 编译器的运行与使用
Vue 的编译器可以在两种环境下工作:
- 构建时编译:这是最常见的做法。在使用
vue-loader或@vitejs/plugin-vue等构建工具时,模板会在项目构建(打包)阶段就被预编译成渲染函数。这样做的好处是最终打包产物体积更小,性能更好,因为浏览器无需承担编译的开销 。 - 运行时编译:如果你直接在代码中使用了字符串模板(例如,通过
template选项),并且引入了包含编译器的完整版 Vue,那么编译过程将在浏览器中实时进行。这会增加最终包的体积,通常不推荐在生产环境中使用 。