综述:
-
我们都知道,每个Vue的应用都是通过new一个Vue构造函数从而创造出来一个vm实例对象,el(elect)配置项为通过id选择器#root选择index页面中的根dom元素进行绑定,data配置项则为vue模板中用到的源数据。
-
Vue实例在创建时,会接收data对象,并遍历此对象所有的属性,并使用Object.defineProperty将属性全部转为getter/setter,以便追踪属性的变化(obs)。当用户在View层进行操作时,ViewModel能感知到变化并对Model层的数据进行更新,反之亦然。
数据代理:
-
数据代理即为通过操作对象a来直接触碰对象b达到修改对象b的目的的操作。
-
在Vue响应式原理中,数据代理则体现在了data配置项通过一系列操作(下文中会分析)赋到了vm实例的_data属性上,然后vm对_data做了代理,即通过Object.defineProperty为vm实例响应式地添加了_data中的属性,从而使Vue模板中不必使用this._data.name来使用,直接使用this.name即可。(Ps:凡是vm实例身上的属性,在Vue模板中均可直接使用,即插值语句{{name}}即可)
-
而在某种意义上_data与data相同。
tips:
一旦data中数据发生改变,那么页面中用到该数据的地方自动更新。
_data代理了data,vm代理了_data。
通过defineProperty()方法为vm添加_data中有的属性,再提供setter/getter。
疑点:data经加工后vm._data===data的原因,修改data属性能够重新渲染页面的原因。
原数据data已经被obs劫持,只能通过obs访问。
_data里面的属性已经变成访问器属性。
_data直接赋值new Vue时传入的data,所以指向同一个地址,二者无区别。
数据劫持:
上文中提到某种意义上data===_data,这里将介绍数据代理图示中黄色线中所做的操作。
-
首先data代表vm实例配置项中的源数据,其需要经过加工后,赋给vm._data,所以说二者相同。
-
至于是如何加工的,下图中的代码做了模拟,用到了观察者对象。
-
首先data模拟的是未经加工的源数据,将data传入了观察者对象(即obs)的构造函数作为参数(即obj)。传入的为对象data,为其地址。
tips:
在Java中,函数参数传递的是对象的引用(内存地址),而不是对象本身。当一个对象作为参数传递给一个函数时,实际上是将对象的引用(内存地址)传递给了函数,函数中对该对象的操作会影响到原始对象。因此,在函数内部对传递的对象进行修改时,会影响到原始对象的状态。
由此,下文中obs,data,._data操作的为同一块内存,故data即为._data
-
在观察者对象(即obs)中所作的操作就是把obj(即传入的data源数据)的所有的属性名提取出来,为这个obs通过Object.defineProperty的形式响应式地(即getter、setter)添加data中的所有属性(若有深层对象则递归添加)。
-
现在这个obs身上有了data中的所有属性及其响应式。
-
之所以采用obs,是为了避免递归死循环。
-
在下图代码中,然后就是将这个构造好的obs连续赋给data及vm._data(实际中为引用传参)
-
在此时,若._data属性值修改,很明显将触发对应属性匹配的setter,而这个setter具有执行解析模板、生成虚拟dom、新旧dom的diff算法对比、生成新页面的功能逻辑。
-
而数据劫持就是体现在这个setter的实际操作中。
-
在某种意义上,观察者obs相当于一个辅助对象。
-
在下图中可以看到,._data身上属性的setter和getter,其对源data通过obs套了一层代理,系统命名为reactiveSetter和reactiveGetter,因为其中封装了更新页面的逻辑操作,所以不同。
-
而且可以看到,._data实际上即为Observer对象,即._data中属性改动将触发Setter渲染页面。
-
当._data中任意一个reactiveSetter被触发后, 都会使Vue重新解析页面,所以说属性修改后页面重新解析的关键在于Setter。
Vue响应式整体流程:
-
Vue框架是一个MVVM框架。
(详见我的上一篇博文)MVVM模型
VM即为我们new出来的实例对象,M即为Model(data源数据),V即为View视图(Vue模板template) 。
简而言之,就是在Vue中,ViewModel就是Vue实例。Vue实例在创建时,会接收data对象,并遍历此对象所有的属性,并使用Object.defineProperty将属性全部转为getter/setter,以便追踪属性的变化(obs)。当用户在View层进行操作时,ViewModel能感知到变化并对Model层的数据进行更新,反之亦然。
DOM Listeners:
DOM Listeners是对DOM结构进行监听,若发生数据改动则通过DOM listenersDOM监听器将数据传到Model中。
即当vm身上的根属性发生改动(即DOM结构发生改变)后,由于vm对其属性._data做了一层数据代理,其对根属性的改动将通过vm身上的该根属性对应的setter来实现对._data中的对应属性的改动,而._data中属性改动又将通过obs传导到Model中。
Data Bindings:
Data Bindings是把Model的数据通过Data Bindings数据绑定送到Vue的模板实例对象上。
即data源数据通过obs传导到._data中,一般情况下vm实例又对._data进行了代理,从而通过vm实例对._data的代理实现了将Model(data)中传来的数据绑定送到Vue模板实例对象的操作。
又因为使用Object.defineProperty,故实现的是使用getter和setter的数据双重绑定操作。
- 改属性页面变化原理大致如下图所示:
(其中Observer的setter与getter即为上文中提到的reactiveGetter/reactiveSetter。)
- 以下代码为简单的响应式模拟(Object.defineProperty应用):
//源数据
let person = {
name:'张三',
age:18
}
//模拟Vue2中实现响应式
let p = {}
Object.defineProperty(p,'name',{
configurable:true,
get(){ //有人读取name时调用
return person.name
},
set(value){ //有人修改name时调用
console.log('有人修改了name属性,我发现了,我要去更新界面!')
person.name = value
}
})
Object.defineProperty(p,'age',{
get(){ //有人读取age时调用
return person.age
},
set(value){ //有人修改age时调用
console.log('有人修改了age属性,我发现了,我要去更新界面!')
person.age = value
}
})