Vue 模板是如何编译的

55 阅读4分钟

Vue 的模板编译是一个将开发者编写的模板(通常是在 .vue文件或 template选项中的类 HTML 代码)转换为可执行渲染函数的过程。这个过程是 Vue 实现响应式渲染的核心,它确保了用户界面能够高效地响应数据变化。 为了让你能快速把握全局,下图清晰地展示了模板编译的完整流程和最终目的:

image.png

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,那么编译过程将在浏览器中实时进行。这会增加最终包的体积,通常不推荐在生产环境中使用 。