事件循环在调用栈清空后先执行所有微任务,待微任务队列为空再执行一个宏任务,形成“宏任务→清微任务”循环;微任务包括promise.then、queuemicrotask等,宏任务包括settimeout、i/o回调等,浏览器中微任务后可能触发渲染。

调用栈清空后,事件循环立即开始下一轮检查:先处理所有已就绪的微任务(如 Promise.then、MutationObserver),等微任务队列彻底为空,再从宏任务队列(如 setTimeout、setInterval、I/O 回调)中取出一个最老的任务推入调用栈执行。
微任务优先于宏任务执行
只要调用栈变空,事件循环不会直接跳去取宏任务,而是先清空当前轮次的所有微任务。这意味着:
- 多个
Promise.then会连续执行,中间不穿插任何宏任务 -
queueMicrotask添加的任务也会被纳入本轮微任务队列 - 哪怕在微任务中又注册了新的微任务,它们仍属于“本轮”,会继续执行直到队列耗尽
宏任务按先进先出顺序逐个触发
微任务清空后,事件循环才从宏任务队列头部取一个任务(比如最早到期的 setTimeout 回调),推入调用栈执行。注意:
- 不同浏览器或 Node.js 版本对宏任务类型的优先级可能有细微差异(如 I/O 回调 vs
setTimeout) - 每个宏任务执行完,都会再次回到微任务检查环节,形成“宏任务 → 清微任务 → 宏任务 → 清微任务…”的循环
-
setImmediate(Node.js)和requestIdleCallback(浏览器)属于特殊宏任务,时机更靠后
渲染时机与事件循环的关系
在浏览器环境中,微任务清空、下一个宏任务尚未开始前,有机会进行页面渲染(如果存在待更新的样式或布局变化)。也就是说:
-
Promise.then中修改 DOM,不会触发同步重排,但会在本轮微任务结束后、下次宏任务前参与渲染 -
setTimeout(fn, 0)的回调一定在下一轮事件循环,所以总在渲染之后执行 -
requestAnimationFrame是一种特殊的宏任务,它在渲染前被调度,优先级高于普通setTimeout
事件循环不是定时轮询,而是在每次调用栈清空后主动检查任务队列,驱动整个异步流程向前推进。











