📌 本文是《前端打怪笔记》系列第 1 篇,适合用于复习、输出、准备面试
✅ 写在前面:你不是不懂,是没真正吃透
事件循环 几乎是前端人绕不过的坎,但也总是似懂非懂。
你可能已经:
- 看了 5 张不同的流程图
- 刷了 3 遍教学视频
- 背过“宏任务和微任务”规则
但真正遇到面试题,比如:
Promise.then
和setTimeout
谁先执行?
async/await
究竟属于哪类任务?
process.nextTick
和queueMicrotask
谁优先?
脑子还是“嗡”的一下。
这篇文章就是为了让你 真正理解,而不是记答案。
🧠 一句话解释事件循环:JS 是单线程,但支持异步
JavaScript 是 单线程运行时:一次只能执行一个任务。
但我们又能写异步逻辑:比如定时器、网络请求、用户操作。JS 怎么做到“非阻塞”呢?
答案就是:事件循环机制(Event Loop)
那它是怎样执行的呢?
JS 引擎从任务队列中不断地调度任务 —— 执行一个宏任务 → 清空微任务 → 继续下一轮宏任务。
- 主线程:JS 的唯一运行通道,只能同时跑一个任务。
- 执行栈:任务正在执行时,代码的“调用路径”。
- 任务队列:未来要执行的代码排队等待。
主线程中,存在:
- 同步任务:立即执行的代码(如
console.log()
) - 异步任务:不会立即执行,注册后进入 任务队列 等待,由浏览器安排时间再执行(如
setTimeout()
)
所有异步任务都要依赖事件循环调度!
一图看懂 JS 执行顺序:
那么哪些是宏任务、哪些又是微任务呢?
🧱 宏任务 vs 微任务
类型 | 常见任务来源 | 什么时候执行 |
---|---|---|
宏任务 | setTimeout 、setInterval 、UI 渲染 、MessageChannel | 一轮事件循环的起点 |
微任务 | Promise.then 、queueMicrotask 、MutationObserver | 当前宏任务执行完立即执行 |
记住:一个宏任务执行完 → 清空所有微任务 → 执行下一个宏任务
⚙️ 那 Web Worker 属于什么任务?
直接给结论,Web Worker 属于独立线程运行,不占用主线程时间。
但是需要注意:
Worker发回的消息会被主线程当作 普通的宏任务 处理(和
setTimeout
同级)
// 主线程
console.log("1. 开始炒菜");
const worker = new Worker("worker.js");
worker.postMessage("切土豆丝"); // 让Worker去干活
worker.onmessage = (e) => {
console.log("4. 收到切好的土豆丝"); // 主线程在宏任务中处理消息
};
console.log("2. 炒菜中...");
setTimeout(() => { console.log("3. 定时器任务"); }, 0);
// worker.js
self.onmessage = (e) => {
// 模拟耗时计算
for (let i=0; i<1e9; i++) {} // Worker线程卡死,但主线程不卡!
self.postMessage("切好了");
};
输出顺序:
1 → 2 → 3 → 4(Worker计算耗时,但主线程不等待,先执行其他宏任务)
✍️ 举几个栗子彻底理解输出顺序
1. 来个简单的练练手
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);
输出顺序: 1 → 4 → 3 → 2
执行过程:
- 执行同步任务:
console.log(1)
- 注册
setTimeout
(宏任务),加入任务队列 Promise.then()
加入微任务队列- 执行同步任务:
console.log(4)
- 清空微任务:输出
3
- 下一轮宏任务:输出
2
2. async/await 属于什么任务?
async function test() {
console.log('a');
await Promise.resolve();
console.log('b');
}
test();
console.log('c');
输出顺序: a → c → b
拆解说明:
await
会将console.log('b')
拆成一个微任务- 所以整个
async
函数执行到await
暂停,主线程继续跑console.log('c')
- 主线程执行完后 → 微任务触发 → 输出
b
3. 来道高频面试题试试手
先不看答案自己尝试解答
console.log('script start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
Promise.resolve()
.then(() => {
console.log('promise1');
return Promise.resolve();
})
.then(() => {
console.log('promise2');
});
queueMicrotask(() => {
console.log('queueMicrotask');
});
console.log('script end');
拆解过程:
- 先执行同步任务
console.log('script start')
- 注册
setTimeout
(宏任务),加入任务队列 Promise.then()
加入微任务队列(promise1
)promise2
属于promise1
微任务执行完成后才执行,所以先观察下面还有没有别的微任务queueMicrotask
加入微任务队列- 执行同步任务
console.log('script end')
执行结果:
script start
script end
promise1
queueMicrotask
promise2
setTimeout
你有没有答对呢…
💬 面试怎么答才显得专业?
❓ 面试官:“你能解释下 JS 的事件循环吗?”
你可以这样答:
- 事件循环是 JS 控制异步执行顺序的机制。
- 所有任务分为同步任务和异步任务。异步任务进一步划分为宏任务和微任务。
- JS 每一轮事件循环会执行一个宏任务,执行完后清空所有微任务。
- 微任务优先于下一轮宏任务执行,所以 Promise.then 总是先于 setTimeout。
- async/await 本质上是 Promise 的语法糖,会拆成两个微任务。
下次在面试中再被问到就应该 自信吟唱 了。😄
📌 小结:写给自己复习用
知识点 | 要点 |
---|---|
事件循环机制 | 宏任务 → 微任务 → 下一轮宏任务 |
同步任务 | 立即执行的代码 |
异步任务 | 注册进队列等待(分宏/微任务) |
微任务 | Promise.then 、queueMicrotask ,总是在宏任务后立即执行 |
async/await | 会被拆成 .then() 形成的微任务 |
Web Worker | 提供多线程能力,但与主线程隔离,靠 postMessage 通信 |
面试答法 | 清晰区分同步/异步 + 宏/微任务 + 举例输出顺序 |
如果你觉得这篇文章对你有帮助,欢迎点赞 👍、收藏 ⭐、评论 💬 让我知道你在看!
后续我也会持续输出更多 前端打怪笔记系列文章,敬请期待!❤️