当你写下:
vue
<template>
<div class="title">{{ message }}</div>
</template>
Vue 内部究竟发生了什么?
"模板是如何变成可执行的 JavaScript 函数的?" "为什么
{{ message }}
能自动更新?" "静态内容是如何被优化的?"
本文将带你深入 Vue 编译器核心,揭秘 Template → AST → Render 函数 的完整链路。
一、核心流程:三步编译流水线
text
Template (字符串)
↓
parse() → AST (抽象语法树)
↓
optimize() → 优化 AST(标记静态节点)
↓
generate() → Render 字符串
↓
new Function() → Render 函数
↓
执行 → VNode → DOM
二、第一步:parse
→ 生成 AST
✅ 目标
将模板字符串转换为 抽象语法树(AST),用 JavaScript 对象描述模板结构。
📌 示例模板
html
<div class="title" v-if="show">
Hello {{ name }}
<span>Static Text</span>
</div>
🌲 生成的 AST 结构
js
{
type: 1, // 元素节点
tag: 'div',
attrsList: [
{ name: 'class', value: 'title' },
{ name: 'v-if', value: 'show' }
],
children: [
{
type: 3, // 文本节点
text: 'Hello ',
static: false
},
{
type: 2, // 表达式节点
expression: '_s(name)',
text: '{{ name }}',
static: false
},
{
type: 1,
tag: 'span',
children: [
{ type: 3, text: 'Static Text', static: true }
],
static: true // 整个 span 是静态的
}
],
static: false,
staticRoot: false
}
🔧 parse
如何工作?
- 正则匹配:顺序扫描模板字符串;
- 识别标记 :
<div>
→ 开始标签 → 执行start()
回调;</div>
→ 结束标签 → 执行end()
回调;Hello {{ name }}
→ 文本 → 执行chars()
回调;
- 构建树结构:通过栈结构维护父子关系。
💡 类似 HTML 解析器,但输出是 AST 而非 DOM。
三、第二步:optimize
→ 静态节点优化
✅ 目标
标记永不变化的节点,在后续更新中跳过 diff,极大提升性能。
🎯 优化策略
节点类型 | 是否静态 | 判断依据 |
---|---|---|
<span>Static</span> |
✅ | 纯文本,无绑定 |
{{ message }} |
❌ | 包含变量 |
<div v-if="false"> |
❌ | 指令依赖响应式数据 |
<img src="./logo.png"> |
✅ | 属性无绑定 |
📊 优化后 AST 新增标记
js
{
tag: 'span',
static: true, // 标记为静态节点
staticRoot: true, // 标记为静态根(可提升)
children: [/* ... */]
}
⚡ 优化带来的性能收益
- 首次渲染:静态节点生成的 VNode 可被缓存;
- 更新渲染 :直接复用缓存的 VNode,跳过
patch
; - 内存节省:减少不必要的 VNode 创建。
四、第三步:generate
→ 生成 Render 函数代码
✅ 目标
将优化后的 AST 转换为可执行的 render
函数字符串。
🧩 生成的 Render 字符串
js
with(this) {
return (show)
? _c('div', { staticClass: "title" }, [
_v("Hello " + _s(name)),
_c('span', [_v("Static Text")])
])
: _e()
}
🔍 关键函数解析
函数 | 作用 | 源码对应 |
---|---|---|
_c |
createElement |
创建 VNode |
_v |
createTextVNode |
创建文本节点 |
_s |
toString |
转换表达式 |
_e |
createEmptyVNode |
创建空节点 |
🏗️ generate
如何工作?
- 深度优先遍历 AST;
- 根据节点类型生成对应代码 :
- 元素节点 →
_c(tag, data, children)
- 文本节点 →
_v(text)
- 表达式 →
_s(expression)
- 元素节点 →
- 处理指令 :
v-if
→ 三元表达式;v-for
→_l(array, function)
v-on
→ 事件监听对象
五、最终:创建 Render 函数
js
const render = new Function(`with(this){return ${code}}`)
✅ render
函数执行结果
js
// 执行 render() 返回 VNode
{
tag: 'div',
data: { staticClass: 'title' },
children: [
{ text: 'Hello Vue', ... },
{ tag: 'span', children: [...], ... }
]
}
六、Vue 3 的编译优化
🚀 编译时优化
优化 | 说明 |
---|---|
静态提升 | 将静态节点提升到 render 外,只创建一次 |
Patch Flag | 动态节点打标记,diff 时只比对相关部分 |
Tree Shaking | 未使用的转换函数被摇除 |
📌 Vue 3 Render 函数示例
js
import { createElementVNode as _createElementVNode } from 'vue'
const _hoisted_1 = /*#__PURE__*/ _createElementVNode("span", null, "Static Text", -1 /* HOISTED */)
return function render(_ctx, _cache) {
return (_ctx.show)
? (_openBlock(), _createElementVNode("div", { class: "title" }, [
_createTextVNode("Hello " + _toDisplayString(_ctx.name), 1 /* TEXT */),
_hoisted_1
], 64 /* STABLE_FRAGMENT */))
: _createEmptyVNode()
}
💥
-1
表示该节点永不更新 ,1
表示只 diff 文本内容。
七、开发模式 vs 生产模式
模式 | 编译时机 | 调试支持 |
---|---|---|
开发模式 | 运行时编译(vue.js) | 模板错误定位精确 |
生产模式 | 构建时编译(vue.runtime.js) | 体积小、性能高 |
✅ 推荐:使用 vue-cli / Vite 在构建时编译模板。
💡 结语
"Vue 模板不是魔法,而是编译的艺术。"
阶段 | 输入 | 输出 | 目的 |
---|---|---|---|
parse |
模板字符串 | AST | 结构化描述 |
optimize |
AST | 优化 AST | 标记静态内容 |
generate |
优化 AST | Render 字符串 | 生成可执行代码 |
掌握模板编译过程,你就能:
✅ 理解 {{ }}
和指令的底层原理;
✅ 优化模板结构提升性能;
✅ 调试编译错误更高效;
✅ 为学习 Vue 3 编译优化打下基础。