人人都会AI编程

6.1 模板编译三阶段:解析(parse)、优化(optimize)、生成(generate)

更新时间:2026-07-09

你在 .vue 文件里写的 <template> 并不是直接跑在浏览器里的 HTML,它会被 Vue 的编译器转成一段纯 JavaScript 的渲染函数。这个过程分为三步:解析、优化、生成。运行时实际执行的,就是编译产出的渲染函数,模板本身并不参与运行。

解析阶段:把模板字符串变成 AST

编译器拿到模板字符串后,首先要读懂它。这个过程很像浏览器解析 HTML:逐字符读取,识别出开始标签、结束标签、属性、文本内容、插值表达式 {{ }} 以及各种指令。

<div id="app">
  <span>Hello</span>
  <p>{{ message }}</p>
</div>

这一步会生成一棵抽象语法树(AST),每个节点描述了它在模板中的类型(元素节点、文本节点、插值节点)、标签名、属性和父子关系。比如上面的模板会被结构化成一棵树,其中 <span>Hello</span>div 的子节点,“Hello” 是一个纯文本节点,{{ message }} 是一个插值节点。

解析阶段还会处理一些模板特有的语法,比如 v-forv-if、动态属性 :src、事件 @click,并把它们全部规范化到 AST 的对应字段中。最终产出的 AST 是后续两个阶段的基础,也保留了源码位置信息,方便开发工具提示和报错。

优化阶段:标记动静,为运行时减负

直接拿 AST 生成渲染函数也能跑,但 Vue 3 的编译器会额外做一次优化——分析哪些节点是“静态的”(永远不会变),哪些是“动态的”(可能随响应式数据变化),并在 AST 上打上标记。这是 Vue 3 渲染性能大幅提升的关键所在。

具体来说有两类核心优化:

1. 静态节点提升
如果一个节点及其所有子节点都不包含任何动态绑定(没有插值、指令、动态属性),它就是一个纯静态节点。优化阶段会把这种节点直接提取到渲染函数外部,并在 AST 上标记。这样在组件多次更新时,静态节点的 VNode 创建只会执行一次,后续 Diff 阶段直接复用,完全跳过对比。

2. PatchFlags 动态标记
对于动态节点,编译器会根据它变化的类型打上 PatchFlag。例如:

  • 1:文本内容会变({{ msg }}
  • 2:class 会变(:class="cls"
  • 4:style 会变
  • 8:props 会变

运行时 Diff 时,Vue 只看带 PatchFlag 的动态节点,并且只检查它们对应变化的属性,不需要全属性对比。

经过优化阶段,AST 上多了 hoistedpatchFlag 等标记信息,为生成阶段产出高效代码做准备。

生成阶段:把 AST 变成渲染函数代码

有了带优化标记的 AST,编译器最后一步就是递归遍历这棵树,生成一段可执行的 JavaScript 代码。这段代码就是组件的渲染函数。

例如,前面的模板经过生成阶段会变成类似这样的代码(简化示意):

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "Hello", -1 /* HOISTED */)

export function render(_ctx, _cache) {
  return (_openBlock(), _createElementBlock("div", { id: "app" }, [
    _hoisted_1,
    _createElementVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ]))
}

注意几点:

  • 静态 <span> 被提升到了 _hoisted_1,渲染函数直接引用它,不需要每次重新创建。
  • 动态 <p> 的创建调用了 _createElementVNode,并且末尾带了个 1(PatchFlag TEXT),告诉运行时“只关心文本内容是否变化”。
  • _openBlock()_createElementBlock() 用于创建 Block 树,配合 PatchFlag 实现靶向更新。

在构建阶段(比如通过 Vite 的 @vitejs/plugin-vue),这些编译工作在打包时就已经完成,发送到浏览器里的只是最终的渲染函数代码,不包含任何编译器。所以用户下载的体积更小,运行时性能也更好。

整个编译过程就像翻译一本带插图的书:解析是把句子拆成单词和语法结构,优化是给那些永远不会变的段落加盖“静态章”,生成则是把整本书用目标语言重新写出来,并且处处考虑如何让读者读得更快。