nextTick

nectTick

在 Vue.js 中,其会异步执行 DOM 更新;当观察到数据变化时,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 watcher 被多次触发,只会一次推入到队列中。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际(已去重的)工作。Vue 在内部尝试对异步队列使用原生的 Promise.thenMutationObserver,如果执行环境不支持,会采用 setTimeout(fn, 0) 代替。Vue.js 选择使用 MicroTask 来进行数据更新,是为了保证能够在当前界面渲染的 Task 执行完毕之后即得到最新的界面,而不是历经两次渲染,从而提高效率。

而当我们希望在数据更新之后执行某些 DOM 操作,就需要使用 nextTick 函数来添加回调:

// HTML
<div id="example">{{ message }}</div>;

// JS
var vm = new Vue({
  el: "#example",
  data: {
    message: "123"
  }
});
vm.message = "new message"; // 更改数据
vm.$el.textContent === "new message"; // false
Vue.nextTick(function() {
  vm.$el.textContent === "new message"; // true
});

在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:

Vue.component("example", {
  template: "<span>{{ message }}</span>",
  data: function() {
    return {
      message: "没有更新"
    };
  },
  methods: {
    updateMessage: function() {
      this.message = "更新完成";
      console.log(this.$el.textContent); // => '没有更新'
      this.$nextTick(function() {
        console.log(this.$el.textContent); // => '更新完成'
      });
    }
  }
});

src/core/util/env

/** 使用 MicroTask 来异步执行批次任务 */
export const nextTick = (function() {
  // 需要执行的回调列表
  const callbacks = []; // 是否处于挂起状态

  let pending = false; // 时间函数句柄

  let timerFunc; // 执行并且清空所有的回调列表

  function nextTickHandler() {
    pending = false;
    const copies = callbacks.slice(0);
    callbacks.length = 0;
    for (let i = 0; i < copies.length; i++) {
      copies[i]();
    }
  } /* istanbul ignore if */ // nextTick 的回调会被加入到 MicroTask 队列中,这里我们主要通过原生的 Promise 与 MutationObserver 实现

  if (typeof Promise !== "undefined" && isNative(Promise)) {
    let p = Promise.resolve();
    let logError = err => {
      console.error(err);
    };
    timerFunc = () => {
      p.then(nextTickHandler).catch(logError); // 在部分 iOS 系统下的 UIWebViews 中,Promise.then 可能并不会被清空,因此我们需要添加额外操作以触发

      if (isIOS) setTimeout(noop);
    };
  } else if (
    typeof MutationObserver !== "undefined" &&
    (isNative(MutationObserver) || // PhantomJS and iOS 7.x
      MutationObserver.toString() === "[object MutationObserverConstructor]")
  ) {
    // 当 Promise 不可用时候使用 MutationObserver
    // e.g. PhantomJS IE11, iOS7, Android 4.4
    let counter = 1;
    let observer = new MutationObserver(nextTickHandler);
    let textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
      characterData: true
    });
    timerFunc = () => {
      counter = (counter + 1) % 2;
      textNode.data = String(counter);
    };
  } else {
    // 如果都不存在,则回退使用 setTimeout
    /* istanbul ignore next */
    timerFunc = () => {
      setTimeout(nextTickHandler, 0);
    };
  }

  return function queueNextTick(cb?: Function, ctx?: Object) {
    let _resolve;
    callbacks.push(() => {
      if (cb) {
        try {
          cb.call(ctx);
        } catch (e) {
          handleError(e, ctx, "nextTick");
        }
      } else if (_resolve) {
        _resolve(ctx);
      }
    });
    if (!pending) {
      pending = true;
      timerFunc();
    } // 如果没有传入回调,则表示以异步方式调用

    if (!cb && typeof Promise !== "undefined") {
      return new Promise((resolve, reject) => {
        _resolve = resolve;
      });
    }
  };
})();
下一页