按照之前响应式原理中的绑定方式会存在一个问题,那就是当前实例的所有属性发生改变时它的setter都会触发,即使这个属性并没有被使用过。
依赖绑定
Dep(dependence)
既然属性被修改时会触发其对应的setter,那么当他被使用时自然也会触发其对应的getter。 简单实现:首先定义一个Dep类,它维护一个订阅者数组, 提供addSub,removeSub,notify方法
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
let _arr = this.subs;
_arr.splice(_arr.findIndex(v => v === sub))
}
// 订阅者必须提供update方法。
notify() {
for(let sub of this.subs) {
sub.update();
}
}
}
Watcher
订阅者,当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触dep对象的notify,通知所有Watcher对象触发update方法。
class Watch {
constructor(vm, cb) {
this._cb = cb;
this.vm = vm;
Dep.target = this;
this.update();
}
update() {
this._cb.apply(this.vm)
}
}
依赖收集
将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改出发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。
class Mvvm {
constructor(options) {
//...
this.watcher = newWatcher(this.vm, this.cb);
}
};
function defineReactive (obj, key, val, cb) {
/*在闭包内存储一个Dep对象*/
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: ()=>{
if (Dep.target) {
/*Watcher对象存在全局的Dep.targe t中*/
dep.addSub(Dep.target);
}
},
set:newVal=> {
/*只有之前addSub中的函数才会触发*/
dep.notify();
}
})
}
完整代码
function observer(obj, cb) {
if (typeof obj !== 'object' || !obj) {
return;
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key], cb)
})
}
function defineReactive(obj, key, val, cb) {
observer(val);
let dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
if(Dep.target) {
dep.addSub(Dep.target)
}
return val;
},
set: newVal => {
dep.notify();
return newVal
}
})
}
function _proxy(data, vm) {
Object.keys(data).forEach(key => {
Object.defineProperty(vm, key, {
enumerable: true,
configurable: true,
get: () => vm._data[key],
set: newVal => {
vm._data[key] = newVal
}
})
})
};
Dep.target = null;
class Mvvm {
constructor(options) {
this._data = options.data;
this.cb = options.render;
_proxy(this._data, this);
observer(this._data, this.cb)
this.watcher = new Watcher(this.vm, '123' ,this.cb);
}
};
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
this.subs.splice(this.subs.findIndex(v => v === sub));
}
notify() {
this.subs.forEach(v => {
v.update();
})
}
}
class Watcher {
constructor(vm, expOrfn, cb, options) {
this.vm = vm;
this.cb = cb;
Dep.target = this;
}
update() {
this.cb.apply(this.vm)
}
}