Vue 的 $nextTick

130

本人小白,如有问题,还望指出,虚心求教。

核心思想

宏任务和微任务,参考本文

语法

  • 全局API: Vue.nextTick(cb)
  • 实例方法: vm.$nextTick(cb)

唯一差别是 $nextTick 调用,回调函数的 this 指向默认是vm

源码相关

/*  */

  var isUsingMicroTask = false;

  var callbacks = [];
  var pending = false;

  function flushCallbacks () {
    pending = false;
    var copies = callbacks.slice(0);
    callbacks.length = 0;
    for (var i = 0; i < copies.length; i++) {
      copies[i]();
    }
  }

  // Here we have async deferring wrappers using microtasks.
  // In 2.5 we used (macro) tasks (in combination with microtasks).
  // However, it has subtle problems when state is changed right before repaint
  // (e.g. #6813, out-in transitions).
  // Also, using (macro) tasks in event handler would cause some weird behaviors
  // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
  // So we now use microtasks everywhere, again.
  // A major drawback of this tradeoff is that there are some scenarios
  // where microtasks have too high a priority and fire in between supposedly
  // sequential events (e.g. #4521, #6690, which have workarounds)
  // or even between bubbling of the same event (#6566).
  var timerFunc;

  // The nextTick behavior leverages the microtask queue, which can be accessed
  // via either native Promise.then or MutationObserver.
  // MutationObserver has wider support, however it is seriously bugged in
  // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
  // completely stops working after triggering a few times... so, if native
  // Promise is available, we will use it:
  /* istanbul ignore next, $flow-disable-line */
  if (typeof Promise !== 'undefined' && isNative(Promise)) {
    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;
  } else if (!isIE && typeof MutationObserver !== 'undefined' && (
    isNative(MutationObserver) ||
    // PhantomJS and iOS 7.x
    MutationObserver.toString() === '[object MutationObserverConstructor]'
  )) {
    // Use MutationObserver where native Promise is not available,
    // e.g. PhantomJS, iOS7, Android 4.4
    // (#6466 MutationObserver is unreliable in IE11)
    var counter = 1;
    var observer = new MutationObserver(flushCallbacks);
    var textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true
    });
    timerFunc = function () {
      counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
    isUsingMicroTask = true;
  } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
    // Fallback to setImmediate.
    // Technically it leverages the (macro) task queue,
    // but it is still a better choice than setTimeout.
    timerFunc = function () {
      setImmediate(flushCallbacks);
    };
  } else {
    // Fallback to setTimeout.
    timerFunc = function () {
      setTimeout(flushCallbacks, 0);
    };
  }

  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;
      })
    }
  }

timerFunc 函数

根据浏览器环境使用异步操作执行 flushCallbacks

flushCallbacks 函数

遍历 callbacks 队列,获取回调函数并执行,重置 pending 状态为 false

pending

标记等待执行flushCallbacks的状态。true 表示已经进行微(宏)任务队列,false 表示还未加入微(宏)任务队列。

callbacks

存放 回调函数 的队列,先进先出。(flushCallbacks函数从索引0开始执行)

执行流程

  1. 首先根据浏览器环境,设置 timerFunc 函数(这是核心实现),优先使用 Promise,不支持则使用MutationObserver ,这两个用来创建微任务,都不支持则使用宏任务(setImmediate 和 setTimeout)。
  2. timerFunc 函数 根据选择异步操作调用执行 flushCallbacks函数(从 callbacks 获取回调函数执行)。
  3. 当调用 nextTick ,先将 回调函数 cb 推入 callbacks 队列,然后判断状态 pending 是否为true (已经处于待执行回调过程(flushCallbacks)),则不做其他处理,如果为 false ,则需要调用 timerFunc 函数 开启等待执行flushCallbacks,并更新状态 pendingtrue
  4. 最后等到微任务(宏任务)执行完后,执行 timerFun函数 内部逻辑。最后更新状态 pendingfalse

image.png