💡 前言
很多初学者都以为 JavaScript 是“单线程+异步”,但当真正遇到 setTimeout、Promise.then、async/await 等异步代码时,却经常被执行顺序搞晕。
这背后,其实就是事件循环机制(Event Loop)在掌控一切。
🧭 一、JavaScript 执行模型总览
JavaScript 是 单线程的,采用了 事件循环机制(Event Loop) 来实现异步处理。主要分为三部分:
-
宏任务(Macro Task)
-
微任务(Micro Task)
-
执行栈(Call Stack)
🧱 二、什么是宏任务(Macro Task)
宏任务是浏览器或 Node.js 提供的异步 API,它们会在 事件循环的每一轮执行一次。
📌 常见宏任务:
-
setTimeout -
setInterval -
setImmediate(Node.js) -
MessageChannel(浏览器) -
整个
script代码块(主任务)
🔬 三、什么是微任务(Micro Task)
微任务是在当前宏任务执行完之后、进入下一轮宏任务之前,执行的一批任务。
📌 常见微任务:
-
Promise.then,catch,finally -
queueMicrotask -
MutationObserver(浏览器) -
process.nextTick(Node.js,优先级更高)
🔁 四、事件循环(Event Loop)是如何调度的?
浏览器中的事件循环流程:
一个宏任务执行开始 →
执行同步代码 →
执行所有微任务(清空微任务队列) →
浏览器执行渲染 →
进入下一轮事件循环
Node.js 中的事件循环流程:
Node.js 的事件循环更复杂,有多个阶段:
timers → pending → idle → poll → check → close callbacks
每个阶段执行完后都会清空微任务队列
🧪 五、经典例子:你能猜出输出顺序吗?
console.log('start');
setTimeout(() => {
console.log('macro-task: setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('micro-task: Promise');
});
console.log('end');
✅ 输出顺序:
start
end
micro-task: Promise
macro-task: setTimeout
🔍 解释:
-
主线程先执行同步代码:
start → end -
Promise.then加入微任务队列,setTimeout加入宏任务队列 -
同步代码执行完毕后,先清空微任务队列
-
最后进入下一轮事件循环,执行宏任务中的
setTimeout
🎯 六、微任务队列是怎么执行的?
-
是一个队列(FIFO),按顺序执行
-
每次宏任务执行完后,清空微任务队列
-
新加入的微任务如果是当前轮产生的,也会立即执行
例子:
Promise.resolve().then(() => {
console.log('A');
Promise.resolve().then(() => {
console.log('B');
});
});
输出:
A
B
🆚 七、浏览器 vs Node.js 的事件循环差异
| 特性 | 浏览器 | Node.js |
|---|---|---|
| 宏任务结构 | 单一阶段 | 多阶段(timers, check 等) |
| 微任务类型 | Promise, queueMicrotask | Promise, process.nextTick |
| 微任务清空时机 | 每个宏任务后 | 每个阶段后 |
setTimeout vs setImmediate | setTimeout 总是先执行 | 顺序不确定(取决于阶段) |
🧠 八、如何利用微任务优化代码?
-
在数据变化后通过
Promise.resolve().then(...)代替setTimeout(..., 0),更快执行逻辑 -
避免在微任务中产生无限递归微任务,可能导致主线程卡死
-
使用
queueMicrotask更加语义化地创建微任务(适用于 polyfill 或框架封装)
✍️ 九、总结
| 关键词 | 概念简述 |
|---|---|
| 宏任务 | 整体任务的单元,事件循环一轮一个宏任务 |
| 微任务 | 更细粒度的异步任务,宏任务后立即执行 |
| 事件循环 | 控制宏任务与微任务调度的机制 |
| 浏览器 | 清晰:宏 → 微 → 渲染 |
| Node.js | 多阶段 + 特殊微任务(nextTick) |
📌 最后一句话总结:
微任务决定“快”,宏任务决定“先后”!
理解事件循环,是写好异步 JavaScript 的核心。
👋 如果你也遇到类似问题,欢迎留言一起讨论。 👍 如果觉得有帮助,欢迎点赞、收藏、关注支持我~