在JavaScript的事件循环(Event Loop)机制中,微任务(Microtask)扮演着至关重要的角色。其优先级仅次于同步代码,这意味着在当前宏任务(Macro-task)执行完毕后、下一个宏任务开始之前,引擎会以最高优先级将微任务队列一次性清空——它不会排队等候、不会延迟执行、也不会跳过任何一项。这种调度策略使得微任务在处理高确定性和低延迟的异步逻辑时,拥有无可比拟的优势。

微任务的执行时机非常明确:只要同步代码全部执行完毕,调用栈(Call Stack)变为空,引擎就会立刻检查微任务队列。只要队列不为空,就持续取出并执行,直到彻底清空——这个过程不会被任何宏任务打断。
- Promise的then、catch、finally方法注册的回调函数一旦被触发,会立刻进入微任务队列,而不会等待下一轮事件循环才执行。
- MutationObserver的回调会在DOM变更之后批量触发,也属于微任务,能保证在浏览器渲染之前及时响应。
- queueMicrotask(fn)是显式向微任务队列插入任务的API,相比Promise.then更加轻量且没有副作用。
再看微任务与宏任务的关系:setTimeout、setInterval、I/O回调、UI渲染、requestAnimationFrame等都属于宏任务,它们必须等当前微任务队列清空后才能轮到自己执行。
- 即使将setTimeout的延迟设置为0毫秒,它的回调函数仍然排在下一轮宏任务队列的头部,永远晚于本轮所有的微任务。
- Vue框架中的nextTick方法正是基于微任务实现的,它确保在数据更新之后、DOM重绘之前执行回调。
- async/await函数中,await之后的代码实际上也被包装成了微任务进行调度。
然而,需要注意一个关键陷阱:微任务并不是无限嵌套安全的。如果一个微任务内部又创建了新的微任务(例如在Promise.then中再次调用Promise.resolve().then),队列就会持续增长,可能阻塞后续的宏任务甚至导致页面卡顿。
- 递归调用queueMicrotask或连续链式Promise.then,很容易引发所谓的“微任务风暴”,导致队列无限增长。
- 虽然浏览器对单次事件循环中的微任务数量设有限制,但过度使用仍然会严重影响页面响应性。
- 当需要批量更新UI时,可以考虑使用requestIdleCallback或任务分片(chunking)技术,避免微任务队列被过度占用。
说到底,微任务机制并非仅仅是语法糖,而是JavaScript引擎调度策略的核心组成部分——它使高确定性、低延迟的异步逻辑成为可能,但同时也要求开发者对其执行边界保持清醒认知。
