# 模板引擎
# vue 中如何解析模板,指令如何处理
问题:
模板是什么 render函数 render函数与 vdom
# 模板是什么
<div id='app'>
<div>
<input v-model="title"/>
<button v-on:click="add">submit</button>
</div>
<ul>
<li v-for="item in list">
{{item}}
</li>
</ul>
</div>
2
3
4
5
6
7
8
9
10
11
这是一个模板
那么模板是什么呢?
- 本质是字符串,是以字符串存在的,只不过像html
- 有逻辑,比如判断,循环这些,如v-if,v-for等,怎么会有逻辑呢,之前写html就没逻辑
- 与html格式很像,但有很大区别。首先html在语法上是不认识v-if,v-for这些的。第二个是html是静态的,没有逻辑,vue是动态的,有逻辑的。它们只是格式很像
- 但是最终模板还是要转换为html来显示的。
那么它是怎么做到的呢?
首先模板最终必须转换成js代码,因为:
- 模板有逻辑,有v-if,v-for。必须用js才能实现(图灵完备的语言)
- 模板要转化为html渲染页面,必须用js才能实现
因此,模板最终要转换成一个js函数(render函数,也就是渲染函数)
# 首先了解下with
var obj = {
name: 'zhangsan',
age: 20,
getAddress: function(){
alert('beijing');
}
}
// 不用with
function fn(){
alert(obj.name);
alert(obj.age);
obj.getAddress();
}
fn();
// 使用width
function fn1(){
with(obj){
alert(name);
alert(age);
getAddress();
}
}
fn1();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
在实际开发中,尽量不要使用with。
fn是我们正常的使用。
fn1使用with的情况。
两个是同样的效果,用with的里面,都不写是谁的属性,是谁统一的,用with包起来。 这个可读性可能没那么强。
# render函数
template 模板 通过 Compile 编译 得到 render函数。
compile 编译可以分成 parse、optimize 与 generate 三个阶段,最终需要得到 render function。
compile 编译过程,现阶段可以不需要完全掌握,只需要了解 解析的大致流程即可。
# 简单的例子
我们看最简单的一段模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="../lib/vue-2.4.0.js"></script>
</head>
<body>
<div id="app">
<p>{{price}}</p>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
price:100
}
})
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
把模板摘出来
<div id="app">
<p>{{price}}</p>
</div>
2
3
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="./vue-2.5.13.js"></script>
</head>
<body>
<div id="app">
<p>{{price}}</p>
</div>
<script>
/**
* _c : 创建dom标签
* _v : 创建文本节点
* _s : toString
*/
var vm = new Vue({
el: '#app',
data: {
price: 100
}
})
// 这个模板最终生成的函数是下面这个
// 以下是手写的 render 函数
function render() {
with(this) { // this 就是 vm
return _c(
'div',
{
attrs: {'id': 'app'}
},
[
_c('p', [_v(_s(price))])
]
)
}
}
// 把函数翻译一下,也就是不用 with
function render1() {
return vm._c(
'div',
{
attrs: {'id': 'app'}
},
[
vm._c('p', [vm._v(vm._s(vm.price))])
]
)
}
</script>
</body>
</html>
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
这个this就是vm这个实例,这个_c就是vm._c,用来创建dom标签的。
第一个参数是个div。
第二个参数是个对象,对象里面有属性。
第三个参数是个数组,数组里面只有一个元素,这个_c肯定也是vm._c。
这个_c第一个参数是p,第二个参数是个数组,里面也是一个数组,_v(_s(price)),这里面的price肯定是vm.price,就是data.price。
然后前面的_s就是vm._s,就是toString函数。
_v也是vm._v,用来创建文本节点的。
总结:
模板中所有信息都包含在render函数中。
this即vm
price即this.price即vm.price即data.price。
_c即this._c即vm._c
# todo-list demo的render函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- 模板 -->
<div id="app">
<input v-model='title'/>
<button v-on:click='add'>submit</button>
<ul v-for='item in list'>
{{item}}
</ul>
</div>
<!-- 源码 -->
<script src="./vue-2.6.10.js"></script>
<script>
var data = {
title: '',
list: []
}
// 初始化 vue 实例
var vm = new Vue({
el: '#app',
data: data,
methods: {
add: function(){
this.list.push(this.title);
this.title = ''
}
}
})
</script>
</body>
</html>
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
39
然后通过这个例子,看看vue的render函数是什么样子的,在源码搜索code.render,然后打印出来。
这是与上面模板对应的 render 函数。
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
},
[
_c('input', {
directives: [{ // 当title发生变化,会赋值给value,从而响应到input
name: "model",
rawName: "v-model",
value: (title),
expression: "title"
}],
domProps: {
"value": (title)
},
on: { // v-model相关 input 里面的数据改变,事件监听到,会赋值给 title
"input": function($event) {
if ($event.target.composing) return;
title = $event.target.value
}
}
}),
_v(" "),
_c('button', {
on: {
// 绑定 click 事件
"click": add
}
},
[_v("submit")]
),
_v(" "),
_l(
// v-for相关
(list),
function(item) {
return _c('ul', [_v("\n " + _s(item) + "\n ")])
}
)
], 2)
}
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
39
40
41
42
43
44
这就是这个 demo 所对应的 render 函数。
在创建input的时候第二个参数有个directives。叫做指令,指令名字是model。value是title,也就是vm.title。
后面_v(''),_v表示创建文本节点,主要是input和button有个换行,如果不换行,就不会有 _v去创建一个空的文本节点。
_l 返回的是一个数组,针对list返回li的标签。
现在可以根据 todo-list demo 的 render 函数,回顾一下 render 函数中:
- v-model 是怎么实现的?
- v-on 是怎么实现的?
- v-for 是怎么实现的?
# 渲染-vue的模板如何被渲染成html
以上已经解决了模板中“逻辑”的问题(v-for v-if)
还剩下模板生成 html 的问题
另外,vm._c 是什么?render 函数返回了什么?
先复习一下 vdom 的知识
这里 vm._c 跟 snabbdom 里面的h()函数非常相似。
vm._c 返回 vnode。那么 render 函数返回的也是 vnode。
可以这样理解:
- vm._c 其实就相当于 snabbdom 中的 h 函数
- render 函数执行之后,返回的是 vnode
vue的模板如何被渲染成html
vm._update(vnode) {
const prevVnode = vm._vnode;
vm._vnode = vnode;
if(!prevVnode){
vm.$el = vm._patch_(vm.$el, vnode); // 第一次没有值
} else {
vm.$el = vm._patch_(prevVnode, vnode); // 有值的情况
}
}
function updateComponent(){
// vm._render即上面的render函数,返回vnode
vm._update(vm._render())
}
2
3
4
5
6
7
8
9
10
11
12
13
14
render 函数和 vdom
- updateComponent 中实现了 vdom 的 patch
- 页面首次渲染执行 updateComponent
- data 中每次修改属性,执行 updateComponent
# 问题解答
模板是什么?
模板:字符串,有逻辑,嵌入 JS 变量……
模板必须转换为 JS 代码(有逻辑、渲染 html、JS 变量)
render 函数执行是返回 vnode
updateComponent
- updateComponent 中实现了 vdom 的 patch
- 页面首次渲染执行 updateComponent
- data 中每次修改属性,执行 updateComponent