阅读 52

Vue 的 $nextTick

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

核心思想

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

语法

  • 全局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

文章分类
前端
文章标签