这个 api 的源码很早就看过,只不过始终没有总结,因而这边总结一下。

这个 api 是怎么应用的

nextTick 在 Vue 中有两种用法,一种是作为全局办法 Vue.nextTick 应用,还有一种是挂载在组件实例上,通过 vm.$nextTick 的形式应用。作为实例办法调用的时候,回调的 this 主动绑定到调用它的实例上。

参数:

// {Function} [callback]// {Object} [context]Vue.nextTick([callback, context])

用法:

在下次 DOM 更新循环完结之后执行提早回调。在批改数据之后立刻应用这个办法,获取更新后的 DOM。

// 批改数据vm.msg = 'Hello'// DOM 还没有更新Vue.nextTick(function () {  // DOM 更新了})

2.1.0 起新增:如果没有提供回调且在反对 Promise 的环境中,则返回一个 Promise 。

// 作为一个 Promise 应用 (2.1.0 起新增,详见接下来的提醒)Vue.nextTick()  .then(function () {    // DOM 更新了  })

源码解析

Vue.nextTick 的源码在这个目录下:

node_modules/vue/src/core/util/next-tick.js

这边的代码举荐从第 33 行开始看:

let timerFuncif (typeof Promise !== 'undefined' && isNative(Promise)) {  const p = Promise.resolve()  timerFunc = () => {    p.then(flushCallbacks)    if (isIOS) setTimeout(noop)  }  isUsingMicroTask = true} else if (!isIE && typeof MutationObserver !== 'undefined' && (  isNative(MutationObserver) ||  MutationObserver.toString() === '[object MutationObserverConstructor]')) {  let counter = 1  const observer = new MutationObserver(flushCallbacks)  const textNode = document.createTextNode(String(counter))  observer.observe(textNode, {    characterData: true  })  timerFunc = () => {    counter = (counter + 1) % 2    textNode.data = String(counter)  }  isUsingMicroTask = true} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {  timerFunc = () => {    setImmediate(flushCallbacks)  }} else {  timerFunc = () => {    setTimeout(flushCallbacks, 0)  }}

这部分的逻辑非常简单,就是嗅探环境,顺次去检测 Promise -> MutationObserver -> setImmediate -> setTimeout 是否存在,找到存在的就应用它,以此来确定回调函数队列是以哪个 api 来异步执行。

而后再看第 87 行 nextTick 办法定义:

export function nextTick (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()  }  // $flow-disable-line  if (!cb && typeof Promise !== 'undefined') {    return new Promise(resolve => {      _resolve = resolve    })  }}

在下面的代码中,因为要实现新增的返回 Promise 的逻辑,所以略显简单。能够间接看上面简化的逻辑:

export function nextTick (cb?: Function, ctx?: Object) {  callbacks.push(() => {    try {      cb.call(ctx)    } catch (e) {      handleError(e, ctx, 'nextTick')    }  })  timerFunc()}

nextTick 函数接管到一个回调函数的时候,先不去调用它,而是把它 push 到一个全局的 callbacks 数组中。在 nextTick 办法最初,调用了 timerFunc 办法,这个办法其实就是方才嗅探环境的时候抉择的 api ,以 Promise 为例,timerFunc 办法应该是长这样:

const p = Promise.resolve()let timerFunc = () => {  p.then(flushCallbacks)  if (isIOS) setTimeout(noop)}

当调用了 timerFunc 之后,flushCallbacks 办法会在下一轮事件循环被执行。咱们再来看 flushCallbacks 办法定义:

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

flushCallbacks 做的事件很简略,就是把 callbacks 数组外面的办法全副执行一遍,而后清空 callbacks

那么总结一下,nextTick 函数接管到一个回调函数的时候,先不去调用它,而是放到一个全局 queue 队列中,期待下一轮事件循环的时候把这个 queue 的函数顺次执行。

这个队列可能是 microTask 队列,也可能是 macroTask 队列。PromiseMutationObserver 属于微工作队列,setImmediatesetTimeout 属于宏工作队列。

参考

Vue.nextTick - Vue 官网文档