宏任务与微任务的本质区别在于调度时机和队列层级:宏任务由宿主环境注入、事件循环每次执行一个;微任务由JS引擎同步注册、每次宏任务后必须清空整个队列。

宏任务和微任务的本质区别,不在“谁更快”,而在于它们被调度的时机和所属的队列层级不同。JavaScript 引擎不直接决定“哪个先跑”,而是由事件循环(Event Loop)按固定节奏协调:每次只取一个宏任务执行,但只要微任务队列非空,就必须一口气清空它——这个“清空”动作才是微任务看起来“总在前面”的真正原因。
本质区别:来源、队列与调度权归属
宏任务由宿主环境(浏览器或 Node.js)注入任务队列,比如 setTimeout 是浏览器定时器线程触发后通知 JS 主线程的;setInterval、I/O 回调、UI 渲染、甚至整个 <script></script> 标签内容本身,都属于宏任务。它们排队等待事件循环“轮到我”,每次只执行一个。
微任务则由 JavaScript 引擎自身在同步代码执行过程中“当场注册”,比如 Promise.then 不是等某个外部系统回调,而是在 Promise 状态变更时,引擎立刻把它放进微任务队列。它的调度不依赖外部线程,也不受时间延迟影响,只等当前宏任务收尾。
简单说:宏任务是“别人给的任务”,微任务是“自己记下的待办”。
执行顺序:不是“插队”,而是“强制结算”
事件循环每一轮的流程是确定的:
- 执行一个宏任务(例如:当前 script、一个 setTimeout 回调、一次 click 事件处理)
- 立即检查并全部执行完当前微任务队列中的所有任务(哪怕执行中又新增了微任务,也一并追加并执行)
- (浏览器环境下)可能触发 UI 渲染
- 回到第一步,取下一个宏任务
关键点在于“清空”二字——微任务队列不会只执行一个就停,而是持续执行直到为空。所以 Promise.resolve().then(...).then(...) 的两个回调会连续输出,中间不会插入任何宏任务。
常见类型对照(带优先级提示)
注意:process.nextTick(Node.js)比普通微任务还早,它在本轮宏任务结束、微任务开始前就执行,属于“超微任务”;而 requestAnimationFrame 位置特殊,通常在微任务之后、渲染之前运行。
-
典型宏任务:setTimeout、setInterval、I/O 回调、UI 渲染、
<script></script>全局代码、用户事件(click、input) - 典型微任务:Promise.then/catch/finally、queueMicrotask、MutationObserver 回调、process.nextTick(Node.js)
一个能看清链条的示例
这段代码的输出顺序是:1 → 2 → 4 → 5 → 3
console.log(1); // 同步 setTimeout(() => console.log(3), 0); // 宏任务 Promise.resolve() .then(() => console.log(2)) .then(() => console.log(4)) .then(() => console.log(5)); console.log(2); // 同步
解释:
- 同步部分先输出 1 和 2
- 接着清空微任务队列:依次输出 2、4、5(注意:这里第二个
then是第一个then执行后新生成的微任务,仍属本轮清空范围) - 最后才轮到宏任务队列里的
setTimeout,输出 3
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











