Blogger Information
Blog 82
fans 0
comment 1
visits 107646
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
当响应式数据更新的时候,会发生什么?
子龙的博客搬家啦httpswwwcnblogscomZiLongZiLong
Original
795 people have browsed it

本篇作为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我们放到下一章来说

Statement of this Website
The copyright of this blog article belongs to the blogger. Please specify the address when reprinting! If there is any infringement or violation of the law, please contact admin@php.cn Report processing!
All comments Speak rationally on civilized internet, please comply with News Comment Service Agreement
0 comments
Author's latest blog post