这个 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
队列。Promise
和 MutationObserver
属于微工作队列,setImmediate
和 setTimeout
属于宏工作队列。
参考
Vue.nextTick - Vue 官网文档