要说Vue中的nextTick,首先需要讲一下Vue中的异步更新策略,在Vue中数据发生变化之后不是立即反应到真实的DOM上去的,虽然视觉上可能是这样的,但是实际内部变化不是这样的。Vue中数据发生变化之后,会开启一个队列用来缓冲同一事件循环中的发生的所有的数据变化,然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际(已去重的)工作。(感觉和React的setState类似)(文档)
数据变化后DOM不是立即变化的,所以开发人员想在DOM变化后立即做一些事情的时候就需要依靠nextTick的协助了(或者setTimeout,但是这显然不是一个优雅的方式)。
Vue中的nextTick是一个全局的api,在实例中可以直接通过this.$nextTick
调用,他的作用是在修改数据之后,立即使用这个回调函数,可以获取更新后的 DOM。(文档)(ps: 如果当前环境支持promise
则会返回一个promise
对象,否则需要传入callback
)
看下Vue中关于nextTick
的核心源码
let 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)) {
const p = Promise.resolve()
timerFunc = () => {
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)
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)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
可以看到这块nextTick会优先使用Promise
(es6+才支持)、MutationObserver
(新api)这些microtask,如果没有再降级使用setImmediate
(ie node 才支持)、setTimeout
(兜底方案)这些macrotask。
- microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
- 因为兼容性问题,vue不得不做了microtask向macrotask的降级方案