第一步,解析模板成render函数
- with的用法
- 模板中所有信息都被render函数包含
- 模板中用到的data中的属性,都变成JS变量
- 模板中的v-model,v-for,v-on都变成了JS逻辑
- render函数返回vnode
//模板
<div id="app">
<div>
<input type="text" v-model="title">
<button @click="add">add</button>
</div>
<div>
<ul>
<li v-for="(item,index) in lists" :key="index">{{item}}</li>
</ul>
</div>
</div>
//render函数
with (this) {
return _c(
'div',
{
attrs: { "id": "app" }
},
[
_c(
'div',
[
_c(
'input',
{
directives: [
{
name: "model",
rawName: "v-model",
value: (title),
expression: "title"
}],
attrs: { "type": "text" },
domProps: { "value": (title) },
on: {
"input": function ($event) {
if ($event.target.composing) return;
title = $event.target.value
}
}
}
),
_v(" "), // 换行 创建一个空字符串的文本节点
_c(
'button',
{
on: { "click": add }
},
[
_v("add")
]
)
]
),
_v(" "), // 换行 创建一个空字符串的文本节点
_c(
'div',
[_c(
'ul',
_l(
(lists), function (item, index) { return _c('li', { key: index }, [_v(_s(item))]) }
)
)
]
)
]
)
}
第二步,响应式开始监听
- Object.defineProperty
- 将data属性代理到vm上
// vm是一个vue实例
// var vm = new Vue({
// el:'#app',
// data:{
// name:'lisi',
// age: 20
// }
// })
// 我们可以模拟一下,vue是怎么监听name和age的变化的,以及name和age怎么代理到vm实例上的
var vm = {}
var data = {
name: 'lisi',
age:20
}
var key, value
for (key in data) {
// 此时key会命中一个闭包,我们需要一个立即执行函数把每个循环到的key保留下来
(function (key) {
Object.defineProperty(vm, key, {
get: function () {
console.log('get',data[key]) // 监听
return data[key]
},
set: function (newVal) {
console.log('set',newVal) // 监听
data[key] = newVal
}
})
})(key)
}
第三步,首次渲染,显示页面,且绑定依赖
- 初次渲染,执行updateComponent,执行vm._render
- 执行render函数,会访问到data里的数据,也就是访问vm上的数据 (vm.list和vm.title)
- 会被响应式的get方法监听到
- 执行updateComponent,会走到vdom的patch方法
- patch将vnode渲染成DOM,初次渲染完成
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())
}
这里有个问题,为什么要监听get呢?get获取之后,什么都做不了,直接监听set不行吗?比如title被修改了,然后set监听到,直接把页面渲染成最新修改的数据。看原因
- data中有很多属性,有些被用到,有些可能不被用到。
- 被用到的(也就是被访问)会触发get,不被用到的不会触发get
- 未触发get的属性,set的时候我们无需关心。
- 目的是避免不必要的重新渲染。
比如,data有3个属性,这3个属性中,其中title和list都被模板用了,而aaa没有。如果我们不先监听get,而是直接监听set,那么无论哪个属性被修改了,都会被set监听到,从而引起重新渲染。
也就是说,aaa属性,虽然没有用到,当它改变的时候,也会被set监听到,从而重新执行updateComponent,
updateComponent里也会重新执行一遍patch,执行结果虽然没有什么变化,但是只要执行,就会消耗性能,所以我们要避免不必要的重新渲染
所以,我们先监听get,来判断这个属性是否被模板用到,只有被用到的属性改变了,set监听才会去重新渲染页面。而没有走get的属性,set的时候,无需关心。
data = {
title:'',
list:[],
aaa:''
}
第四步,data属性变化,触发rerender
- 修改属性,被响应式的set监听到
- set中执行 updateComponent
- updateComponent重新执行 vm._render
- 生成的vnode和prevnode,通过patch进行对比
- 渲染到html中
var data = {
title:'',
lists:[]
}
var vm = new Vue({
el:"#app",
data:data,
methods:{
add:function(){
if(!this.title){
return
}
this.lists.push(this.title)
this.title = ''
}
}
})
var key, value
for (key in data) {
// 此时key会命中一个闭包,我们需要一个立即执行函数把每个循环到的key保留下来
(function (key) {
Object.defineProperty(vm, key, {
get: function () {
console.log('get',data[key]) // 监听
return data[key]
},
set: function (newVal) {
console.log('set',newVal) // 监听
data[key] = newVal
}
})
})(key)
}
网友评论