文章目录
Vue 数据响应式原理
Vue2.0 对象
完整流程图
Observe类:将正常的object转换为被检测的object
- Observer类的作用将正常的object转换为被侦测的object,通过getter和setter的形式来追踪。
- 在每个属性的getter中会生成对应的Dep对象。属性1 --> Dep对象1 属性2–> Dep对象2 …
- 在每个属性的setter中会通知Dep对象里的watcher对象数据发生变化了
Watcher类
- 在解析el模板中的指令的时候,通过get获取到数据,创建对应的watcher对象,将watcher对象存入该数据对应的Dep对象中
- 当数据发生变化时,在每个属性的setter中会通知Dep对象,调用notify()遍历通知watcher调用update()更新函数更新视图
过程详述
问题1:数据被修改后,Vue内部如何监听message数据的改变的? --> Object.defineProperty -> 监听对象属性的改变
问题2:当数据发生改变,Vue如何知道需要通知哪些地方更新界面? -->发布订阅者模式
- Object.defineProperty -> 监听对象属性的改变
defineReactive函数定义一个响应式数据
function defineReactive(data,key,val){
Objcet.defineProperty(data,key,{//代码①
enumerable:true,
configurable:true,
get:function(){
return val;
},
set:function(){
if(val===newVal)return;
val = newVal;
}
})
}
- 定义Observer类将一个正常的object转换成被侦测的object,循环给一个数据内的所有属性(包括子属性)都转换成getter/setter形式,
//{a:"x",b:{c:"y",d:"z"}}
//Observer类的作用将正常的object转换为被侦测的object
class Observer{
constructor(data){
this.data = data;
if(!Array.isArray(data)){ //这里数组和对象都会进来,只处理对象
Object.keys(data).forEach(key=>{
defineReactive(this.data,key,data[key]);
})
}
}
}
function defineReactive(data,key,val){
//如果val是对象,比如b:{c:"y",d:"z"},说明不是最里层,还需要对{c:"y",d:"z"}进行转换getter/setter
if(typeof val === 'object')new Observer(val);//递归的目的是每一层的属性都应该被绑定响应式
//..代码①
}
- 现在对数据绑定了监听,但是当数据发生变化时,我们通知谁?我们怎么知道那些地方使用了这个数据?
对于模板来说使用了name数据,也就是调用了name.get()方法,所以我们可以在get方法中存储用到了name属性的地方,我们可以先把使用了name属性的地方,叫做name属性的依赖。
<template>
{{name}}
</template>
定义Dep类,Dep类的目的是存储使用了某数据的依赖,同时可以删除依赖、给依赖发更新通知等等,那么Dep类与某数据的关系应该是一一对应,所以我们在绑定响应式时,可以为每个属性绑定一个dep对象。在get的时候,将使用了name的地方(依赖)存进dep中,在set数据改变的时候,通知dep中的依赖数据发生改变了,在getter中收集依赖,在stter中触发依赖
class Dep {//Dep类存储依赖,添加依赖, 删除依赖,通知依赖等
constructor(){
this.subs = [];//subs存储依赖
}
addSub(sub){
this.subs.push(sub);
}
depend(){
//if(依赖){
// this.addSub(依赖);
//}
if(window.target){
this.addSub(window.target);
}
}
notify(){
this.subs.forEach(sub =>{
sub.update(); //通知这个属性的所有依赖数据更新
})
}
}
function defineReactive(data,key,val){
if(typeof val === 'object')new Observer(val);
let dep = new Dep(); //绑定dep对象
Objcet.defineProperty(data,key,{
enumerable:true,
configurable:true,
get:function(){
dep.depend();//修改dep对象,这里应该添加依赖
return val;
},
set:function(){
if(val===newVal)return;
val = newVal;
dep.notify();//通知依赖,数据发生了修改
}
})
}
- 依赖是什么?之前我们把使用了数据的地方,叫做依赖,那么依赖描述成数据结构应该是什么样子的?
定义一个Watcher类,一个Watcher对象就是一个依赖
当属性变化时,就会调用dep的notify属性循环通知watcher调用update()方法,修改模板中的数据。
class Watcher{
constructor(vm,name,node){
this.node = node;
this.vm = vm;
this.name = name;
window.target = this, //我们给依赖命名为window.target, = watcher对象,所以依赖就是watcher对象
this.update();
window.target = null; //并没有绑定在实例上,全局仅有一个,数据更新后会重新调用get,防止一个watcher对象被多次加入dep.sub数组中
}
update(){//将{{name}}的name更新为vm里面的值,这里是在vm上代理了_data的值
this.node.nodeValue = this.vm[this.name];
//从vm中取某个属性,相当于调用该属性的getter方法,此时window.target是有值的,值为watcher对象,所以这个watcher对象会被存储进dep.sub数组里面
}
}
//watcher对象在解析模板中的指令的时候会被创建new Watcher
不足以及解决办法
新增属性、删除属性、界面不会更新
解决:通过vm.$set(obj,key.val)
/vm.$delete(obj,key)
新增属性/删除属性
Vue2.0 数组
Array是通过改写数组的push
、pop
、shift
、unshift
、splice
、sort
、reverse
不足以及解决办法
直接通过下标修改数组,界面不会自动更新
解决:vm.$set(arr,index.value)
,调用数组的splice方法,或者采用splice用新值替换该下标值