Vue 的响应式/模版解析/渲染

| 字数 883
  1. 1. 响应式
  2. 2. 模板解析
  3. 3. 总结
  1. 1. 响应式
  2. 2. 模板解析
  3. 3. 总结

        对于使用 vue 的同学来说,响应式、模版解析/渲染应该都很熟悉,vue 开发离不开这些特性。用了 vue 挺长时间了,写写总结。

响应式

创建一个普通的对象:

1
2
3
4
5
let t = {
name: 't1'
age: 19
}
console.log(t.age) // 19 那么如何监听age属性的访问与设置呢?

在es5中加入了一个api叫 Object.defineProperty,该api允许

创建对象的属性自定义 get/set 函数。修改上面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let t = {}
let name = 't1'
Object.defineProperty(obj, 'name', {
get() {
console.log('get')
return name
},
set(newVal) {
console.log('set')
name = newVal
}
})
console.log(obj.name)
// get
// t1
obj.name = 'george'
// set

在vue中我们可以模拟一下,大概过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let vm = {}
let data = {
name: 'lyn',
age: 20
}

for(let v in data) {
(function(v) {
Object.defineProperty(vm, v, {
get() {
return data[v]
},
set(newVal) {
data[v] = newVal
}
})
})(v)
}

为什么要将data的值全部代理至vm对象上?vue中就是这么写的 …,而且模版解析的时候会用到。

模板解析

我们经常在vue中使用vue模板,比如:

1
2
3
4
5
6
7
<div id="app">
<input v-model="title">
<button @click="add"></button>
</div>
<ul>
<li v-for="v in list">{{ v }}</li>
</ul>

模板的本质是字符串,其特点有:

  • 包含 if/for 等逻辑指令
  • 动态的(动态生成 dom )
  • 最终转换为 html

综上,要将模板转换为 html ,只能使用 js 来操作。在 html/css/js 中,只有js能处理逻辑和渲染页面。

在当前文件引入vue文件,打开控制台,在源码中搜索code.render 在 return 前打印 code.render,观察模板被解析后生成的render函数,render函数最终返回vnode。如下:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
with(this){  // this 就是 vm
return _c(
'div',
{
attrs:{"id":"app"}
},
[_c(
'div',
[
_c(
'input',
{
directives:[
{
name:"model",
rawName:"v-model",
value:(title), // vm.title 在这里就将model中的的值通过vm与view中的input数据联系了起来
expression:"title"
}
],
domProps:{
"value":(title)
},
on:{
"input":function($event){
// 在这里可以发现v-model 其实在vue模版内被封装了进去
if($event.target.composing)return;
title=$event.target.value
在这里就将view中的input中的值通过vm与model中的数据联系了起来与上面相对应实现双向绑定
}
}
}
),
_v(" "),
// 因为html中俩个标签间我们换行所以这里是空
_c(
'button',
{
on:{
"click":add
// 相当于 vm.add这里直接调用methods中定义的add函数
}
},
[_v("submit")]
)
]),
_v(" "),
_c('div',
[
_c(
'ul',
//v-for这里使用内部定义的 _l函数来处理
_l((list),function(item){return _c('li',[_v(_s(item))])})
)
]
)]
)
}

_c 创建标签,_v创建文本元素,_s toString(),这个_c 和 snabbdom 中的 h 函数很像,那vue中更新的操作会不会和 snabbdom 中的逻辑页相似呢?
vue 中的更新函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
vm._update(vnode) {
const prevNode = vm._vnode
vm._node = vnode
if(!prevNode) {
vm.$el = vm.__patch___(vm.$el, vnode)
}else{
vm.$el = vm.__patch__(prevNode, vnode)
}
}

function updateComponent() {
vm._update(vm._render())
// vm._render 即生成上面包含 _c/_v/_s的模板解析函数
}

也就是说每次改变data,通过Object.defineProperty监听修改操作,一旦修改就调用 updateComponent 函数,利用vm._render方法生成新的 vnode。通过 vm._patch_ 将vnode转化为html并更新至原生dom中。

总结

  • 解析模板生成render函数
  • 响应式开始监听
  • 首次渲染,显示页面且绑定依赖
  • data属性变化,再次渲染