深入浅出 JavaScript 事件循环:一次彻底搞懂宏任务与微任务
“JavaScript 是单线程的,但它为什么可以处理异步任务?”
“为什么 Promise 比 setTimeout 先执行?”
“微任务到底有多微?”
这些问题,绕不开一个关键词:事件循环(Event Loop)。今天我们就用通俗的语言,讲清楚 JS 异步背后的“神秘机制”。
什么是事件循环?
事件循环是 JavaScript 用来处理同步和异步代码执行顺序的一种机制,它确保:
同步代码立即执行,异步代码在合适的时机执行。
更正式地说:
事件循环是 JavaScript 执行环境中调度任务执行的核心机制。它负责从任务队列中依次取出任务并执行,确保单线程的 JavaScript 能够以非阻塞的方式处理异步操作。
为什么需要事件循环?
JavaScript 是单线程的 —— 所有代码只能一个接一个执行。但在实际应用中我们经常要做:
- 网络请求
- 读取文件
- 定时器
- 等待用户点击
这些事如果“卡”在主线程里,整个页面就“假死”了。于是,事件循环机制就登场了:它允许这些操作“异步地”执行,并在结果准备好后重新排入任务队列。
一、JavaScript 是单线程的
JavaScript 设计之初就是运行在浏览器中的“脚本语言”,初衷就是操作网页。网页 UI 渲染也只能有一个线程,如果 JS 能多线程同时改 DOM,那页面得乱套。
于是,JavaScript 是单线程的。
但现代 Web 不可能没有异步:网络请求、定时器、点击事件……那 JS 是怎么做到“又单线程又异步”的呢?
这就要靠主角:事件循环(Event Loop)
二、事件循环是怎么工作的?
简单来说,事件循环就像一个无限循环的工厂,它在不停地执行任务。但它有严格的流程:
- 执行一个宏任务(比如主线程代码或
setTimeout
)- 执行所有微任务(比如
Promise.then
)- 浏览器进行UI 渲染
- 回到第 1 步
注意:每个宏任务执行完之后,都会清空所有的微任务!
三、宏任务 和 微任务 到底是什么?
✅ 宏任务(Macro Task)
由浏览器发起的“大的任务块”,比如:
- 整体脚本(主线程代码)
setTimeout
setInterval
setImmediate
(Node 专属)requestAnimationFrame
宏任务之间执行是“分开的”,一个执行完,才轮到下一个。
✅ 微任务(Micro Task)
由 JavaScript 自己产生的“更细粒度的异步任务”,比如:
Promise.then
/catch
/finally
queueMicrotask
MutationObserver
微任务会在当前宏任务结束后、下一个宏任务开始前全部执行完。
四、实际运行顺序示例
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');