深入浅出手撕简易VUE.JS和MVVM原理(四)

129 阅读2分钟

这是我参与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等等方法中可以实例化观察者,具体的实现留待下一次最终章详细说明,下次见,加油!