JavaScript事件循环按“同步代码→微任务→宏任务”顺序循环执行;微任务在每个宏任务后立即清空,优先级高于宏任务,如Promise.then总在setTimeout前执行。

JavaScript 事件循环机制,是单线程环境下实现“看似并行”异步操作的核心调度规则。它不是靠多线程,而是靠一套明确的执行顺序:同步代码 → 微任务 → 宏任务 → 微任务 → … 循环往复。
宏任务和微任务的本质区别
宏任务和微任务不是“谁更高级”,而是**进入队列的时机不同、执行优先级不同、来源不同**:
- 宏任务:代表一个相对完整的执行单元,比如整个 script 脚本、setTimeout 回调、setInterval、I/O 操作、UI 渲染(浏览器中)、postMessage 等。每次事件循环只取一个宏任务执行。
- 微任务:是更轻量、更高优先级的任务,比如 Promise.then/catch/finally 的回调、queueMicrotask()、MutationObserver、Node.js 中的 process.nextTick。每个宏任务执行完后,会立刻清空全部微任务(包括新产生的),再继续下一个宏任务。
为什么微任务总比宏任务先执行?
这不是“偏爱”,而是事件循环的硬性流程规定:
- 主线程执行完当前宏任务(比如刚跑完一段同步代码或 setTimeout 回调);
- 立即检查微任务队列,只要不为空,就一个个执行,直到队列清空;
- 此时才去宏任务队列取下一个任务(比如下一个 setTimeout)。
这个设计保证了 Promise 链式调用能及时响应,也避免了 UI 渲染被大量微任务阻塞(因为微任务不会打断一次渲染)。
立即学习“Java免费学习笔记(深入)”;
一个典型执行顺序示例
看这段代码:
console.log('A');setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));
console.log('D');
输出一定是:A → D → C → B。原因很清晰:
- A 和 D 是同步代码,直接进调用栈执行;
- setTimeout 回调被放入宏任务队列;
- Promise.then 回调被放入微任务队列;
- 同步代码执行完,调用栈空了,事件循环先处理微任务(C),再取下一个宏任务(B)。
实际开发中容易踩的坑
理解错宏微任务顺序,常导致逻辑时序混乱:
- 在 setTimeout 里改状态,又立刻用 Promise.then 去读——可能读到旧值,因为 then 是微任务,执行得比 setTimeout 回调早;
- 用 await 处理异步,误以为它“阻塞”后续代码——其实 await 后面的代码会被包装成微任务,仍遵循微任务规则;
- 连续多个 Promise.then 嵌套,以为会分多次宏任务执行——其实它们都在同一轮微任务中依次执行,中间不会穿插其他宏任务。











