当响应式数据更新的时候,会发生什么?

本篇作为vue响应式原理的最后一篇,作为最后一个总结,比起我们helloworld的例子我们新的例子长这样:

template:
<div id="app">
    {{msg}}
    <div @click="change">{{test}}</div>
</div>
script:
new Vue({
        el:'#app',
        watch:{
            msg:function(newer,old){
                console.log(newer,old)
            }
        },
        data:function(){
            return{
                msg: 'hello,world'
            }
        },
        computed:{
            test: function(){
                return this.msg + '嗨,世界'
            }
        },
        methods:{
            change(){
                this.msg = '10086'
            }
        }
    })

我们从点击事件发生的这一刻,开始追踪,当change事件发生后,首先会激活data里的msg上的set方法:

set: function reactiveSetter (newVal) {
        var value = getter ? getter.call(obj) : val;
        /* eslint-disable no-self-compare */
        if (newVal === value || (newVal !== newVal && value !== value)) {
          return
        }
        /* eslint-enable no-self-compare */
        if (customSetter) {
          customSetter();
        }
        // #7981: for accessor properties without setter
        if (getter && !setter) { return }
        if (setter) {
          setter.call(obj, newVal);
        } else {
          val = newVal;
        }
        childOb = !shallow && observe(newVal);
        dep.notify();
      }

此时,根据前文介绍,dep里的watcher应该有三个,第一个是watch的,第二个是组建的,第三个是computed的,然后,开始执行,这里注意到当一个响应式数据的值前后如果相等的话,set是会直接返回的,只有不相同的时候才回去执行下面的方法,也就是dep.notify:

Dep.prototype.notify = function notify () {
    // stabilize the subscriber list first
    var subs = this.subs.slice();
    if (!config.async) {
      // subs aren't sorted in scheduler if not running async
      // we need to sort them now to make sure they fire in correct
      // order
      subs.sort(function (a, b) { return a.id - b.id; });
    }
    for (var i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
      console.log(i)
    }
  }

一般我们的config.async都是true,表明我们组建的更新是异步的,然后执行每个warcher的update方法:

Watcher.prototype.update = function update () {
    /* istanbul ignore else */
    if (this.lazy) {
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this);
    }
  };

结合前面的知识可以清楚的知道,这里,computed上的watcher的this.lazy是true,因此这里在执行computed上watcher的update方法的时候,只会简单的将他的watcher的dirty标为true,而在执行watch和组建的watcher上的update方法的时候,会调用queueWatcher方法:

 /**
   * Push a watcher into the watcher queue.
   * Jobs with duplicate IDs will be skipped unless it's
   * pushed when the queue is being flushed.
   */
  function queueWatcher (watcher) {
    var id = watcher.id;
    if (has[id] == null) {
      has[id] = true;
      if (!flushing) {
        queue.push(watcher);
      } else {
        // if already flushing, splice the watcher based on its id
        // if already past its id, it will be run next immediately.
        var i = queue.length - 1;
        while (i > index && queue[i].id > watcher.id) {
          i--;
        }
        queue.splice(i + 1, 0, watcher);
      }
      // queue the flush
      if (!waiting) {
        waiting = true;

        if (!config.async) {
          flushSchedulerQueue();
          return
        }
        nextTick(flushSchedulerQueue);
      }
    }
  }

queueWatcher在flush掉一个watcher之前,只会将同一个watcher往队列里添加一次,如果没有开始刷新队列,则将其push进队列,如果已经开始刷新了,就把他按从小到大的顺序添加到合适的位置,然后因为waiting默认值为false,开始走nextTick,next开始之前,注意到,这里传进去了一个函数,flushSchedulerQueue:

/**
   * Flush both queues and run the watchers.
   */
  function flushSchedulerQueue () {
    currentFlushTimestamp = getNow();
    flushing = true;
    var watcher, id;

    // Sort queue before flush.
    // This ensures that:
    // 1. Components are updated from parent to child. (because parent is always
    //    created before the child)
    // 2. A component's user watchers are run before its render watcher (because
    //    user watchers are created before the render watcher)
    // 3. If a component is destroyed during a parent component's watcher run,
    //    its watchers can be skipped.
    queue.sort(function (a, b) { return a.id - b.id; });

    // do not cache length because more watchers might be pushed
    // as we run existing watchers
    for (index = 0; index < queue.length; index++) {
      watcher = queue[index];
      if (watcher.before) {
        watcher.before();
      }
      id = watcher.id;
      has[id] = null;
      watcher.run();
      // in dev build, check and stop circular updates.
      if (has[id] != null) {
        circular[id] = (circular[id] || 0) + 1;
        if (circular[id] > MAX_UPDATE_COUNT) {
          warn(
            'You may have an infinite update loop ' + (
              watcher.user
                ? ("in watcher with expression \"" + (watcher.expression) + "\"")
                : "in a component render function."
            ),
            watcher.vm
          );
          break
        }
      }
    }

    // keep copies of post queues before resetting state
    var activatedQueue = activatedChildren.slice();
    var updatedQueue = queue.slice();

    resetSchedulerState();

    // call component updated and activated hooks
    callActivatedHooks(activatedQueue);
    callUpdatedHooks(updatedQueue);

    // devtool hook
    /* istanbul ignore if */
    if (devtools && config.devtools) {
      devtools.emit('flush');
    }
  }

这个函数呢,字如其名,就是用来刷新队列的,在刷新队列之前,还将其进行了排序,为啥要排序,英文注释说的很清楚,保证其更新顺序由父到子,保证watch上的watcher更新在组建的watcher之前,当子组件在父组件watcher run的时候被destroy的时候可以跳过他的watcher,然后就开始遍历队列开始执行watcher的run方法了:

Watcher.prototype.run = function run () {
    if (this.active) {
      var value = this.get();
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even
        // when the value is the same, because the value may
        // have mutated.
        isObject(value) ||
        this.deep
      ) {
        // set new value
        var oldValue = this.value;
        this.value = value;
        if (this.user) {
          try {
            this.cb.call(this.vm, value, oldValue);
          } catch (e) {
            handleError(e, this.vm, ("callback for watcher \"" + (this.expression) + "\""));
          }
        } else {
          this.cb.call(this.vm, value, oldValue);
        }
      }
    }
  };

run方法都会调用当前watcher上的get方法,而watcher的get方法里,会调用getter方法,这个方法就是用来收集依赖,以及更新视图的方法,在watch上的watcher是一个闭包函数,会访问一下他watch的vue实例上data里的响应式数据,进而激活他的get方法,在组建的watcher上是一个叫updateComponent方法,用于刷新视图,然后继续走到下面的步骤,如果是watch的watcher就执行ifelse分支的trycatch上的语句,此时就是执行的我们自定义的watch的回调。如果是其他的就执行else分支。

然后run方法执行完毕,回到flushSchedulerQueue 里,此时所有watcher执行完毕之后剩下的东西就是重置一下队列等等一系列后续工作。但是呢flushSchedulerQueue 在实际的运行的时候是在nextTick开始运行的,什么是nextTick我们放到下一章来说

posted @ 2020-08-12 08:31  子龙_子龙  阅读(9)  评论(0编辑  收藏  举报