Vue源码解析_模板编译
发表于更新于
VUEvueVue源码解析_模板编译
OHNII什么是模板编译
模板编译就是把我们写的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
| 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
|
详细流程说明
- 模板编写:开发者在
<template> 中编写模板
- 模板编译:Vue 将模板编译为渲染函数
- 渲染函数执行:渲染函数生成 VNode
- 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
| { 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
| { 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
| 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, TEXT: 2, 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', for: 'item in list', }
|
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
| { type: 1, tag: 'div', attrsList: [ { name: 'class', value: 'container' } ], attrsMap: { 'class': 'container' }, if: 'show', 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
| { 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的模板编译是将模板转换为可执行代码的关键过程,主要包括三个阶段:
关键要点
模板编译的三个阶段
- 解析阶段:将模板字符串解析为AST
- 优化阶段:标记静态节点,提升性能
- 代码生成阶段:将AST转换为render函数
AST是模板编译的核心
- AST是模板的树状结构表示
- 包含元素节点、文本节点、表达式节点
- 记录了节点的所有信息(属性、指令、子节点等)
编译优化提升性能
- 静态节点提升,避免重复创建
- 事件优化,减少函数创建
- 属性优化,分离静态和动态属性
render函数是最终产物
- render函数可以直接执行生成VNode
- 使用with语句绑定this上下文
- 通过_c、_v、_s等辅助函数创建节点
流程图
