依赖收集
为什么要收集依赖
其目的在于我们观察数据的属性值发生改变时,可以通知哪些视图层使用了该数据。
咋样实现收集依赖
通过一个事件发布订阅模式
设计模式,然而在这种设计模式去实现依赖收集,在vue有两个角色(对象),订阅者Dep 和 观察者Watcher
。
-
订阅者Dep
- 为什么引入Dep
收集依赖需要找一个存储依赖
的地方,为此我们创建它用来添加依赖,删除依赖,和 向依赖发送消息。
于是我们先来实现一个订阅者Dep类,用于解耦属性的依赖收集和派发更新操作,说具体点,它主要作用存放 watcher 观察者对象。我们可以把Watcher 理解一个中介的角色
,数据发生变化时通知它,然后它通知其他地方(比如更新操作)。 - 简易实现 Dep
注意class Dep { constructor () { /* 用来存放Watcher对象的数组 */ this.subs = []; } /* 在subs中添加一个Watcher对象 */ addSub (sub) { this.subs.push(sub); } /* 通知所有Watcher对象更新视图 */ notify () { this.subs.forEach((sub) => { sub.update(); }) } }
1. 用 addSub 方法可以在目前的 Dep 对象中增加一个 Watcher 的订阅操作;
2. 用 notify 方法通知目前 Dep 对象的 subs 中的所有 Watcher 对象触发更新操作。
- 为什么引入Dep
-
观察者Watcher
- 为什么引入Watcher
Vue中定义一个watcher表示观察订阅依赖。
当属性值发生改变,我们要通知到数据的地方,而使用这个数据的地方很多,而且类型不一样。可能时模板中,可能是watch methods ...,这时需要抽象出一个集中处理这些情况,然后我们在收集阶段只收集封装好的类的实例进来,通知也通知它一个,再由它负责通知其他地方。 - 简易实现 Watcher
class Watcher { constructor(obj, key, cb) { // 将 Dep.target 指向自己 // 然后触发属性的 getter 添加监听 // 最后将 Dep.target 置空 Dep.target = this this.cb = cb this.obj = obj this.key = key this.value = obj[key] Dep.target = null } update() { // 获得新值 this.value = this.obj[this.key] // 我们定义一个 cb 函数,这个函数用来模拟视图更新,调用它即代表更新视图 this.cb(this.value) } }
- 为什么引入Watcher
依赖收集
所谓的依赖,其实就是Watcher。至于如何收集依赖,总结起来就一句话,在getter中收集依赖,在setter中触发依赖。先收集依赖,即把用到该数据的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循环触发一遍就行了。
具体来说,当外界通过Watcher读取数据时,便会触发getter从而将Watcher添加到依赖中,哪个Watcher触发了getter,就把哪个Watcher收集到Dep中。当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。
最后我们对 defineReactive 函数进行改造,在自定义函数中添加依赖收集和派发更新相关的代码,实现了一个简易的数据响应式。
class Dep {
constructor() {
// 用来存放watcher对象的数组
this.subs = [];
}
// 添加watcher对象
addSub(sub) {
this.subs.push(sub);
}
// 通知所有watcher对象更新视图,通过update 更新
notify() {
this.subs.forEach(sub => {
sub.update();
})
}
}
class Watcher {
constructor(obj,key, cb) {
//将Dep.taget 指向自己
// 然后触发属性的getter添加监听
// 最后将Dep.target置空
Dep.target = this;
this.cb = cb;
this.obj = obj;
this.key = key;
this.value = obj[key];
Dep.target = null;
}
update() {
//获取新值
this.value = this.obj[key];
// 我们顶一个cb 函数, 这个函数用来模拟视图的更新,调用它即代表更新视图
this.cb(this.value);
}
}
//响应式
function render() {
console.log("视图更新")
}
//重写数组方法
let methods = ["pop","push","unshift","shift","sort","reverse","splice"];
let arrayProto = Array.prototype;
let proto = Object.create(arrayProto);
methods.forEach(method => {
proto[method] = function() {
arrayProto[method].call(this, ...arguments);
}
})
//观察者
function observe(obj) {
//判断是否数组,如果是改变数组的原型
if(Array.isArray(Obj)) {
obj._proto_ = proto;
return
}
if(!obj || typeof obj !=="object") {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj,key, obj[key])
})
}
//使用Object.defineProerty 拦截数据/代理数据
function defineReactive(data, key, value) {
observe(value);//递归子属性
let dp = new Dep();//新增一个dep
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get:function getter() {
//将watcher 添加到订阅
if(Dep.target) {
dp.addSub(Dep.target);
}
return value;
},
set: function setter(newValue) {
if(newValue != value) {
value = newValue;
render();//更新视图
dp.notify();//通知更新
}
}
})
}
class Vue {
constructor(options) {
this._data = options.data;
observe(this._data);
// 新建一个watcher对象,这时候Dep.target 指向watcher对象
new Watcher();
console.log("模拟视图渲染")
}
}
网友评论