人人都会AI编程

微任务优先的降级策略:Promise → MutationObserver → setImmediate → setTimeout

更新时间:2026-07-10

nextTick 的核心职责是将回调函数延迟到下一次 DOM 更新之后执行,因此它必须是一个异步操作。问题在于,浏览器的异步任务分为微任务(Microtask)和宏任务(Macro task),它们的执行时机不同:

  • 微任务在当前事件循环的末尾、下一次渲染之前执行,所以能保证在 DOM 更新完成后、浏览器重绘之前立即触发回调。
  • 宏任务则必须等到下一次事件循环,此时浏览器可能已经完成了重绘,时效性不如微任务,但在旧浏览器中可能是唯一可用的异步方案。

因此 nextTick 的理想实现是优先使用微任务,并在微任务不可用时降级到宏任务。Vue 2 的降级策略正是按这个原则设计的,它会依次检测以下 API 的可用性:

1. Promise(微任务,首选)

Promise.resolve().then(callback) 生成一个微任务。所有现代浏览器和 Node.js 都原生支持 Promise,所以它是兼容性与性能俱佳的首选方案。Vue 3 的 nextTick 直接采用了 Promise,不再需要降级。

2. MutationObserver(微任务,备选)

在旧版本浏览器(如 IE11)中,Promise 可用但可能打 polyfill 补丁,Vue 希望用原生微任务来避免 polyfill 带来的不确定性。MutationObserver 原本是监听 DOM 变化的 API,但可以通过创建一个空文本节点并修改其内容来强制触发微任务回调

let counter = 1
const observer = new MutationObserver(callback)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, { characterData: true })
// 触发微任务
counter = (counter + 1) % 2
textNode.data = String(counter)

这是一种巧妙但“非常规”的 hack,IE11 也支持 MutationObserver,所以它成了 Promise 之后的优秀备选。

3. setImmediate(宏任务,仅 IE 和 Node.js)

setImmediate 是宏任务中执行最快的,它设计的初衷就是“尽快执行”,比 setTimeout(func, 0) 更快,因为后者有最少 4ms 的延迟(在嵌套调用时)。但 setImmediate 只有 IE 和 Node.js 实现了,其他浏览器不支持,所以它排在第三顺位。

4. setTimeout(flushCallbacks, 0)(最终降级)

如果以上所有方案都不可用(例如极老的浏览器),Vue 会退回到最原始的 setTimeout,虽然延迟较大,但能保证回调一定会异步执行。这是兜底方案。

Vue 3 的时代:降级已经过时

Vue 3 放弃了这套复杂的降级策略,因为现代浏览器和环境已经全面支持 Promise,没有必要再使用 MutationObserver hack 或 setImmediate。Vue 3 的 nextTick 实现非常干净:

const resolvedPromise = Promise.resolve()
function nextTick(fn) {
  return fn ? resolvedPromise.then(fn) : resolvedPromise
}

为什么你需要了解这个?

对于今天的日常开发,你不需要关心这些细节,因为 nextTick 直接用就好。但理解这套降级策略有几个实用价值:

  • 它解释了为什么 Vue 2 项目在 IE 下也能正常工作,而不会因为 Promise 缺失就崩溃。
  • 它帮助你理解“微任务”与“宏任务”的执行顺序区别,从而在复杂的异步场景中(比如数据更新后立刻获取 DOM 尺寸)能正确选择 nextTick 还是 setTimeout
  • 如果你接手的是一个遗留 Vue 2 项目,并且要处理兼容性问题,了解这段历史能帮你快速定位奇怪行为。

一句话:Vue 2 用尽各种办法,就是为了把回调塞到浏览器渲染前的那一刻;而现在的你,只需要记住 await nextTick() 这样一句简洁的写法就够了。