Vue源码解析_模板编译

什么是模板编译

模板编译就是把我们写的Vue模板转换成JavaScript代码的过程。简单来说,就是:

输入:Vue模板字符串
输出:可执行的render函数

1
2
3
4
5
<!-- 输入:模板 -->
<div id="app">
<h1>{{ message }}</h1>
<button @click="handleClick">点击我</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 输出:render函数
function render() {
with(this) {
return _c('div', {
attrs: {"id": "app"}
}, [
_c('h1', [_v(_s(message))]),
_v(" "),
_c('button', {
on: {"click": handleClick}
}, [_v("点击我")])
])
}
}

整体渲染流程

Vue的完整渲染流程如下:

1
模板编写 → 模板编译 → 渲染函数执行 → VNode渲染 → 真实DOM

详细流程说明

  1. 模板编写:开发者在 <template> 中编写模板
  2. 模板编译:Vue 将模板编译为渲染函数
  3. 渲染函数执行:渲染函数生成 VNode
  4. VNode 渲染:VNode 通过 patch 过程,最终生成真实的 DOM 并插入视图

其中,模板编译是将模板转换为渲染函数的关键步骤。

模板编译的三个阶段

模板编译主要包括以下三个阶段:

1. 解析阶段(Parse)

作用:将模板字符串解析为抽象语法树(AST)

简单理解:就像把HTML代码拆解成一个个节点对象

1
2
3
4
<!-- 模板 -->
<div class="container">
<h1>{{ title }}</h1>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 解析后的AST
{
type: 1,
tag: 'div',
attrsList: [{ name: 'class', value: 'container' }],
children: [
{
type: 1,
tag: 'h1',
children: [
{
type: 2,
expression: '_s(title)',
text: '{{ title }}'
}
]
}
]
}

2. 优化阶段(Optimize)

作用:遍历 AST,标记静态节点,以便后续优化

简单理解:找出哪些节点不会变化,标记出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 优化后的AST
{
type: 1,
tag: 'div',
static: false, // 有动态内容,不是静态的
children: [
{
type: 1,
tag: 'h1',
static: false, // 有插值表达式,不是静态的
children: [...]
}
]
}

3. 代码生成阶段(Generate)

作用:将 AST 转换为渲染函数

简单理解:把AST节点转换成JavaScript代码

1
2
3
4
5
6
7
8
9
10
// 生成的render函数
function render() {
with(this) {
return _c('div', {
staticClass: "container"
}, [
_c('h1', [_v(_s(title))])
])
}
}

AST抽象语法树

什么是AST?

AST(Abstract Syntax Tree)是源代码语法结构的一种抽象表示。简单来说,就是把代码转换成树状结构。

AST节点类型

1
2
3
4
5
const ASTNodeTypes = {
ELEMENT: 1, // 元素节点(如div、span)
TEXT: 2, // 表达式节点(如{{ message }})
TEXT_NODE: 3 // 文本节点(纯文本)
}

AST节点结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 元素节点结构
{
type: 1, // 节点类型
tag: 'div', // 标签名
attrsList: [...], // 属性列表
attrsMap: {...}, // 属性映射
parent: parentNode, // 父节点
children: [...], // 子节点
static: false, // 是否为静态节点
staticRoot: false, // 是否为静态根节点
hasBindings: false, // 是否有动态绑定
if: 'condition', // v-if 条件
for: 'item in list', // v-for 循环
}

AST示例

让我们看一个具体的例子:

1
2
3
4
5
<!-- 模板 -->
<div class="container" v-if="show">
<h1>{{ title }}</h1>
<p>静态文本</p>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 对应的AST
{
type: 1,
tag: 'div',
attrsList: [
{ name: 'class', value: 'container' }
],
attrsMap: {
'class': 'container'
},
if: 'show', // v-if指令
static: false, // 不是静态节点
children: [
{
type: 1,
tag: 'h1',
static: false, // 有插值表达式
children: [
{
type: 2,
expression: '_s(title)',
text: '{{ title }}'
}
]
},
{
type: 1,
tag: 'p',
static: true, // 纯文本,是静态的
children: [
{
type: 3,
text: '静态文本'
}
]
}
]
}

实际应用示例

1. 简单模板编译

1
2
3
4
5
<!-- 原始模板 -->
<div id="app">
<h1>{{ message }}</h1>
<button @click="handleClick">点击我</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 编译后的渲染函数
function render() {
with(this) {
return _c('div', {
attrs: {"id": "app"}
}, [
_c('h1', [_v(_s(message))]),
_v(" "),
_c('button', {
on: {"click": handleClick}
}, [_v("点击我")])
])
}
}

编译过程解析

  • _c('div', ...) → 创建div元素
  • _v(_s(message)) → 创建文本节点,message会被转换为字符串
  • on: {"click": handleClick} → 绑定点击事件

2. 条件渲染模板

1
2
3
4
5
<!-- 原始模板 -->
<div>
<h1 v-if="showTitle">{{ title }}</h1>
<p v-else>没有标题</p>
</div>
1
2
3
4
5
6
7
8
// 编译后的渲染函数
function render() {
with(this) {
return _c('div', [
(showTitle) ? _c('h1', [_v(_s(title))]) : _c('p', [_v("没有标题")])
])
}
}

编译过程解析

  • v-if 被转换为三元运算符
  • (showTitle) ? ... : ... → 条件判断

3. 列表渲染模板

1
2
3
4
5
6
7
8
<!-- 原始模板 -->
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{ item.name }}
</li>
</ul>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
// 编译后的渲染函数
function render() {
with(this) {
return _c('div', [
_c('ul', _l((list), function(item) {
return _c('li', {
key: item.id
}, [_v(_s(item.name))])
}), 0)
])
}
}

编译过程解析

  • _l((list), function(item) {...}) → 列表渲染函数
  • key: item.id → 绑定key属性

4. 组件模板编译

1
2
3
4
5
6
7
8
<!-- 原始模板 -->
<my-component
:prop="data"
@event="handleEvent">
<template #default>
<p>插槽内容</p>
</template>
</my-component>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 编译后的渲染函数
function render() {
with(this) {
return _c('my-component', {
props: {
prop: data
},
on: {
event: handleEvent
}
}, [
_c('template', {
slot: "default"
}, [
_c('p', [_v("插槽内容")])
])
])
}
}

编译过程解析

  • props: { prop: data } → 传递props
  • on: { event: handleEvent } → 绑定事件
  • slot: "default" → 插槽内容

编译过程详解

1. 解析阶段(Parse)

解析阶段的主要任务是识别模板中的各种语法:

1
2
3
4
5
<!-- 模板 -->
<div class="container" v-if="show">
<h1>{{ title }}</h1>
<button @click="handleClick">点击</button>
</div>

解析结果

  • 识别出div元素及其属性
  • 识别出v-if指令
  • 识别出插值表达式Vue源码解析_模板编译
  • 识别出事件绑定@click

2. 优化阶段(Optimize)

优化阶段标记静态节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 优化前
{
tag: 'div',
children: [
{ tag: 'h1', children: [{ type: 2, expression: '_s(title)' }] },
{ tag: 'button', children: [{ type: 3, text: '点击' }] }
]
}

// 优化后
{
tag: 'div',
static: false, // 有动态内容
children: [
{ tag: 'h1', static: false }, // 有插值表达式
{ tag: 'button', static: true } // 纯文本,静态
]
}

3. 代码生成阶段(Generate)

代码生成阶段将AST转换为render函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// AST节点
{
tag: 'div',
attrs: [{ name: 'class', value: 'container' }],
if: 'show',
children: [...]
}

// 生成的代码
_c('div', {
staticClass: "container"
}, [
// 子节点代码...
])

编译优化技巧

1. 静态提升

Vue会将静态节点提升到render函数外部:

1
2
3
4
5
<!-- 模板 -->
<div>
<h1>静态标题</h1>
<p>{{ dynamicContent }}</p>
</div>
1
2
3
4
5
6
7
8
9
10
11
// 编译结果
const _hoisted_1 = _c('h1', [_v("静态标题")])

function render() {
with(this) {
return _c('div', [
_hoisted_1, // 复用静态节点
_c('p', [_v(_s(dynamicContent))])
])
}
}

2. 事件优化

Vue会优化事件处理:

1
2
<!-- 模板 -->
<button @click="handleClick">点击</button>
1
2
3
4
5
6
// 编译结果
_c('button', {
on: {
click: handleClick // 直接引用,不创建匿名函数
}
}, [_v("点击")])

3. 属性优化

Vue会优化属性绑定:

1
2
<!-- 模板 -->
<div :class="dynamicClass" class="static-class">内容</div>
1
2
3
4
5
// 编译结果
_c('div', {
staticClass: "static-class",
class: dynamicClass
}, [_v("内容")])

总结

Vue的模板编译是将模板转换为可执行代码的关键过程,主要包括三个阶段:

关键要点

  1. 模板编译的三个阶段

    • 解析阶段:将模板字符串解析为AST
    • 优化阶段:标记静态节点,提升性能
    • 代码生成阶段:将AST转换为render函数
  2. AST是模板编译的核心

    • AST是模板的树状结构表示
    • 包含元素节点、文本节点、表达式节点
    • 记录了节点的所有信息(属性、指令、子节点等)
  3. 编译优化提升性能

    • 静态节点提升,避免重复创建
    • 事件优化,减少函数创建
    • 属性优化,分离静态和动态属性
  4. render函数是最终产物

    • render函数可以直接执行生成VNode
    • 使用with语句绑定this上下文
    • 通过_c、_v、_s等辅助函数创建节点

流程图