前语

在上一节把数据劫持简略的完成了一下,可是现在咱们只能在操控台中测试看到数据的改变,这节会在原来的基础上逐渐丰厚。这节咱们要完成的是Watcher观察者,即数据改变后视图也会进行更新,此外还会运用Dep订阅器来对观察者进行搜集。

完成Dep订阅器

效果

  1. 增加观察者
  2. 告诉观察者去更新视图

完成

既然要对观察者进行增加,那么咱们能够在Observer.js文件中写出以下代码:

class Dep {
    constructor() {
        this.subs = [];
    }
    // 搜集观察者
    addSub(watcher) {
        this.subs.push(watcher);
    }
    // 告诉观察者
    notify() {
        this.subs.forEach(w => w.update());
    }
}

经过addSub办法完成第一个对观察者的增加,notify办法会告诉每一个观察者去更新视图,所以咱们还需求在观察者中定义一个update办法。

完成Watcher观察者

效果

  1. 检查旧值和新值有没有改变
  2. 有改变则调用更新视图的办法

完成

接着咱们需求继续在Observe.js文件中新增Watcher类,因为咱们首先需求在里面获取到旧值和新值,然后在进行比较,所以咱们需求传递一些能够获取旧值和新值的参数。

class Watcher {
     constructor(vm, expr, cb) {
       this.vm = vm;
       this.expr = expr;
       this.cb = cb;
       this.oldValue=this.getOldValue()
   }
     getOldValue() {
       const oldValue=compileUtil.getVal(this.expr,this.vm);
       return oldValue
    }
}

上述代码中在getOldValue办法中经过再次调用compileUtil中的getVal办法来获取当时的值,其间cb参数是为了把新值回调到模版编译里即更新视图,也便是说会形成一个闭环,别着急接着往下看。

    update() {
        const newValue=compileUtil.getVal(this.expr,this.vm);
        if(newValue!==this.oldValue){
            this.cb(newValue);
        }
    }   

声明update办法来比较旧值和新值,如果有改变就把新的值回调回去。

《源码系列》助你理解vue呼应式源码——完成Watcher观察者

再来看这张图,现在DepWatcher已经创立好了,它们之间怎样进行相关呢?由图可知,需求把ObserverDepDepWatcherCompileWatcher进行相关。

也便是说在订阅数据改变时要在往Dep中增加订阅者,所以这一步能够放在监听数据中的getter的时分进行,可是订阅者Watcher从哪里来呢?这儿就用到了一奇妙的方式,便是在进行模版编译时拿v-html举例,在获取v-html指令对应的值的时分咱们就给它创立一个Watcher观察者并绑定更新函数,这也便是为什么在创立Watcher类中传递cb参数的原因。

    html(node, expr, vm) {
        const value = this.getVal(expr, vm);
        // 绑定对应的watcher 订阅数据改变 绑定更新函数
        new Watcher(vm, expr, (newVal) => {
            this.updater.htmlUpdater(node, newVal)
        })
        this.updater.htmlUpdater(node, value);
    }

绑定了Watcher类之后咱们能够在Watcher类中的getOldValue办法中直接把当时Watcher实例目标的this赋值给Dep,这样一来getter中的Dep就能够顺畅增加观察者了。

    // Watcher
    getOldValue() {
        Dep.target = this;
        const oldValue = compileUtil.getVal(this.expr, this.vm);
        // 确保只增加一次观察者
        Dep.target = null;
        return oldValue
    }
    // Observer
    defineReactive(obj, key, value) {
        this.observer(value)
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get() {
                // 订阅数据改变时往Dep中增加观察者
                Dep.target && dep.addSub(Dep.target)
                return value
            },
        })
    }

在数据发生改变时需求调用Dep中的notify办法,notify办法会根据当时的值与旧值进行比较,有改变就会把新的值回调到更新页面的办法中去,这样就形成了一个闭环!

    defineReactive(obj, key, value) {
        this.observer(value)
        let dep=new Dep();
        Object.defineProperty(obj, key, {
            set: (newVal) => {
                this.observer(newVal)
                if (newVal != value) {
                    value = newVal
                    dep.notify();
                }
            }
        })
    }

这样整个流程基本上就差不多完成了,最后再总结一下吧。

总结

标题:vue是怎么完成呼应式的/vue中的双向数据绑定原理?

vue的双向数据绑定首要是由CompileObserverDepWatcher四部分组成,效果分别是Compile用来初始化视图,对页面中的一些指令或特点进行解析(便是根据书写的一些vue语法在data中找到对应定义的值并烘托到页面中的过程),在这个过程中Compile还会对页面用到的每个值进行观察者创当即绑定更新函数,用于更新视图。

Dep中首要经过addSub办法来增加订阅者以及运用notify办法告诉watcher去更新视图。

Watcher首要用来更新视图,经过getOldValue获取到当时的值与旧值进行比较如果有改变会当即执行在模版编译阶段传递的回调函数进行数据的替换。

Observe的效果便是运用Object.defineProperty办法对所有数据进行劫持监听,在get办法中进行依赖搜集并往Dep中增加订阅者,在set的时分会告诉Dep中的观察者更新视图。

结束撒花!