vue2源码解析之异步更新队列的执行

26 阅读1分钟

我们都知道,new Vue之后,会调用_init方法,_init方法会merge options,然后会调用各种mixin方法,在这个过程中,会调用两个hook,一个是beforecreate,一个是created。最终会调用$mount方法。在这个方法中,会调用beforemount的钩子,然后就会生成vnode,然后就会开始更新操作,更新完,就会插入到dom中。

这个过程了解完之后,接下来,就要研究另外一个问题。就是数据变化之后,vue是如何更新数据,并且让视图也更新的。

下面我们一起来看源码。

      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.notify()方法,我们来看看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();
    }
  };

他核心调用了update方法,就是watcher的update方法。

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

update方法调用了queuewatcher方法,queuewatcher方法我们再看看,有点绕,大家不要着急。

  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方法最终调用了nexttick方法。

  function nextTick (cb, ctx) {
    var _resolve;
    callbacks.push(function () {
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, 'nextTick');
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    if (!pending) {
      pending = true;
      timerFunc();
    }
    // $flow-disable-line
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
  }

nexttick最终执行了timerfunc方法,最终我们看看,timerfunc方法。

    var p = Promise.resolve();
    timerFunc = function () {
      p.then(flushCallbacks);
      // In problematic UIWebViews, Promise.then doesn't completely break, but
      // it can get stuck in a weird state where callbacks are pushed into the
      // microtask queue but the queue isn't being flushed, until the browser
      // needs to do some other work, e.g. handle a timer. Therefore we can
      // "force" the microtask queue to be flushed by adding an empty timer.
      if (isIOS) { setTimeout(noop); }
    };
    isUsingMicroTask = true;

timerfunc很简单,就是在微任务里面去执行相应的函数,去进行后续的更新操作就可以了。总结一下,上面有个方法里面,他把watcher都存起来了,然后这里更新又是等浏览器里面的所有同步代码执行完了以后,也就是同步代码执行完成之前,会等待所有的watcher都加到queue里面去了之后,才更新。

宏观总结一下,其过程就是,值被改写,出发set操作,会通知watcher进行更新,watcher会被放到一个队列里面去,更新函数会被放到一个promise里面,等待同步代码更新完成之后,才更新。