事件循环中,每个宏任务执行完后立即清空全部微任务,再取下一个宏任务;宏任务队列含settimeout、i/o等,微任务队列含promise.then、queuemicrotask等,二者独立不混排。

任务队列的调度顺序,核心就一条:**每个宏任务执行完后,立刻清空全部微任务,再取下一个宏任务**。它不是“谁先注册谁先执行”,而是严格按类型分层、按阶段清空。
宏任务和微任务不是并列排队
很多人误以为所有异步任务都挤在一个队列里,按注册先后排。其实浏览器/Node.js 维护的是两个独立队列:
- 宏任务队列:存放 setTimeout、setInterval、I/O 回调、用户事件(如 click)、script 整体代码等
- 微任务队列:存放 Promise.then/catch/finally、queueMicrotask、MutationObserver 等
这两个队列不混在一起,也不互相抢占;微任务只在“宏任务交接间隙”集中执行。
一次事件循环的完整步骤
事件循环不是持续扫描队列,而是一步一步推进的。每轮循环固定包含以下动作:
- 从宏任务队列取出一个任务(比如一个 setTimeout 回调),推入调用栈执行
- 同步代码执行完毕后,立即检查微任务队列
- 只要微任务队列非空,就不断取出队首任务执行,直到队列彻底清空(期间新产生的微任务也会被追加到队尾并执行)
- 微任务清空后,可能触发 UI 渲染(浏览器环境)
- 回到第一步,取下一个宏任务
典型例子看执行节奏
这段代码能清晰体现调度逻辑:
console.log('1');setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
输出顺序是:1 → 4 → 3 → 2。原因:
- '1' 和 '4' 是同步代码,属于第一个宏任务,立即执行
- 接着清空微任务队列:Promise.then 是微任务,输出 '3'
- 最后才轮到宏任务队列里的 setTimeout 回调,输出 '2'
注意几个关键细节
- script 标签本身就是一个宏任务——整段内联脚本或模块入口,是事件循环跑起来的第一个宏任务
- 微任务可以无限递归生成(比如在 then 里再调 then),但引擎会限制深度,避免阻塞后续宏任务
- UI 渲染通常发生在微任务清空之后、下一个宏任务开始之前(浏览器中),所以连续的微任务不会触发多次渲染
- setImmediate(Node.js)和 requestAnimationFrame(浏览器)虽属宏任务,但插入时机有特殊规则,不完全等同于 setTimeout
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











