vue $nexttick如何实现的
Vue $nextTick 的实现原理
Vue 的 $nextTick 方法用于在 DOM 更新完成后执行回调函数。其核心实现依赖于 JavaScript 的事件循环机制和微任务/宏任务的调度。
基于微任务的异步队列
Vue 在内部维护一个回调队列,$nextTick 会将回调函数推入这个队列。Vue 会根据当前环境选择最优的异步调度方式:
-
Promise(首选):现代浏览器支持的原生微任务 API,优先级最高。
if (typeof Promise !== 'undefined') { const p = Promise.resolve() timerFunc = () => p.then(flushCallbacks) } -
MutationObserver:作为 Promise 的降级方案,同样是微任务。
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) } -
setImmediate:Node.js 环境的高效宏任务。
if (typeof setImmediate !== 'undefined') { timerFunc = () => setImmediate(flushCallbacks) } -
setTimeout:最终的兼容性兜底方案。
timerFunc = () => setTimeout(flushCallbacks, 0)
执行流程
- 回调收集:调用
$nextTick(callback)时,回调被推入callbacks数组。 - 异步触发:通过
timerFunc调度flushCallbacks执行。 - 队列处理:
flushCallbacks会复制当前队列并依次执行所有回调,同时清空队列。
源码关键逻辑
const callbacks = []
let pending = false
function flushCallbacks() {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
function nextTick(cb, ctx) {
callbacks.push(() => {
if (cb) cb.call(ctx)
})
if (!pending) {
pending = true
timerFunc()
}
}
与数据更新的关系
当响应式数据变化时,Vue 的更新也是通过 nextTick 实现的异步队列:
- 数据变更触发
dep.notify() watcher.update()将渲染 watcher 加入队列nextTick(flushSchedulerQueue)确保更新在下次事件循环执行
这种设计保证了:
- 同一事件循环内的多次数据变更只会触发一次渲染
- 在回调中能获取更新后的 DOM
- 避免不必要的重复计算







