JavaScript通过事件循环实现异步,宏任务(如setTimeout)执行后清空所有微任务(如Promise.then),再进入下一宏任务,确保微任务优先于渲染执行。

JavaScript 是单线程语言,靠事件循环(Event Loop)实现异步操作的调度。理解宏任务(MacroTask)与微任务(MicroTask)是掌握 JavaScript 异步执行机制的关键。
事件循环的基本流程
JavaScript 引擎在执行代码时,会先运行同步代码,遇到异步任务则将其交给浏览器或 Node.js 环境处理,并注册回调函数。当异步操作完成,回调会被放入任务队列。事件循环不断检查调用栈是否为空,一旦空了,就从任务队列中取出下一个任务执行。
关键点在于:事件循环每次从宏任务队列中取一个任务执行,执行完后,立即清空当前所有的微任务队列,然后再回到宏任务队列取下一个任务。
宏任务(MacroTask)有哪些?
宏任务是事件循环中一次完整的执行单元。每个宏任务执行完毕后,浏览器可以进行渲染更新。
立即学习“Java免费学习笔记(深入)”;
- setTimeout 回调
- setInterval 回调
- I/O 操作
- UI 渲染
- script 标签整体代码(初始同步代码也被视为一个宏任务)
微任务(MicroTask)有哪些?
微任务在当前宏任务执行结束后立即执行,且会全部执行完才继续下一个宏任务。
- Promise.then/catch/finally 回调
- MutationObserver(DOM 变化监听)
- queueMicrotask() API
- process.nextTick()(Node.js 环境,优先级更高)
执行顺序:宏任务 vs 微任务
来看一个经典例子:
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
输出结果是:1 → 4 → 3 → 2
解释:
- 先执行同步代码:'1' 和 '4'
- 遇到 setTimeout,放入宏任务队列
- 遇到 Promise.then,放入微任务队列
- 当前宏任务结束,检查微任务队列,执行 then 回调输出 '3'
- 进入下一轮事件循环,执行 setTimeout 回调输出 '2'
再看一个复杂一点的例子:
console.log('start');
setTimeout(() => {
console.log('timeout1');
Promise.resolve().then(() => {
console.log('promise in timeout');
});
}, 0);
Promise.resolve().then(() => {
console.log('promise1');
setTimeout(() => {
console.log('timeout in promise');
}, 0);
});
console.log('end');
输出顺序:start → end → promise1 → timeout1 → promise in timeout → timeout in promise
分析:
- 同步代码输出 start、end
- 微任务:第一个 Promise.then 输出 promise1
- 宏任务队列:第一个 setTimeout 触发,输出 timeout1
- 此时该宏任务内部产生微任务:Promise.then 输出 promise in timeout
- 下一轮宏任务:setTimeout 在 promise 中注册的那个触发
注意:微任务总是在当前宏任务末尾被集中执行,即使它是在 setTimeout 回调中创建的。
常见误区与注意事项
很多人误以为 setTimeout(..., 0) 就是“立刻执行”,其实它只是尽快加入宏任务队列,必须等当前所有同步和微任务完成。
微任务的高优先级意味着滥用 Promise.then 做大量计算可能阻塞渲染,因为浏览器只在宏任务之间重绘页面。
在实际开发中,若想让某些操作延迟到当前所有异步回调之后执行,可以用 queueMicrotask 或 Promise.resolve().then() 包裹。
基本上就这些。搞清楚宏任务和微任务的执行时机,就能准确预测异步代码的输出顺序。不复杂但容易忽略细节。










