这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战
一、实现观察者Watcher和依赖收集器Dep
上次说到观察者watcher和依赖收集器Dep,这里给出具体实现。 其中需要实现Dep来存储watcher,一方面需要能够通知控制watcher更新,另一方面是添加watcher的方法。
class Dep {
constructor () {
this.subs = []; // 留着存储观察者们
}
addSub (watcher) { // 添加观察者
this.subs.push(watcher);
}
notify () { // 通知观察者更新
this.subs.forEach((w) => {
w.update();
})
}
}
创建一个Watcher的class,作用就是看看新旧值是否有变化,有就需要做一个更新/回调去更新视图,接收vue实例、expr表达式、回调函数(拿到新值后回调到前面的compile更新,因为compile还是会与watcher有关联)。那么就需要拿到新值和旧值。
class watcher {
constructor (vm, expr, cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
this.oldVal = this.getOldVal(); // 保存旧值
}
getOldVal () {
Dep.target = this; // 将当前观察者挂载到Dep上再去触发get
const oldVal = compileUtil.getVal(this.expr, this.vm);
Dep.target = null; // 挂载完毕需要注销,防止重复挂载 (数据一更新就会挂载)
return oldVal;
}
update () {
const newVal = compileUtil.getVal(this.expr, this.vm); // 获取新值
if (newVal !== this.oldVal) this.cb(newVal); // 更新,回去调更新函数
}
}
那么Dep怎么与observer关联?劫持数据的时候就可以实例化Dep了,而get的时候可以添加观察者。
defineReactive(obj, key, value) { // 劫持和监听数据
this.observe(value); // 首先需要递归遍历当前数据(因为可能多嵌套)
const dep = new Dep(); // 实例化dep
Object.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get() {
Dep.target && dep.addSub(Dep.target); // 确保存在观察者才添加
return value;
},
set: (newVal) => {
this.observe(newVal); // 更新对象时再监听一遍防止新对象没有被监听到
if (newVal !== value) {
value = newVal;
}
dep.notify(); // 通知观察者更新
}
})
}
那么观察者实例化的时机?可以看到应该是在compiler类中订阅数据变化,所以在test、html等等方法中可以实例化观察者,具体的实现留待下一次最终章详细说明,下次见,加油!