前言
在 JavaScript 异步编程中,事件循环(Event Loop) 是绕不开的核心知识点,也是前端面试高频必考题。
很多同学对宏任务、微任务、async/await 执行顺序一知半解,做题频频翻车。
这篇文章我会用通俗的语言 + 清晰的规则 + 经典真题,带你彻底吃透事件循环,面试遇到直接拿捏。
一、为什么需要事件循环?
JavaScript 是单线程语言,同一时间只能执行一段代码。
但我们日常开发中,会遇到大量异步操作:
- 定时器
setTimeout - 网络请求 Ajax/fetch
- Promise 异步回调
- DOM 渲染
为了不阻塞主线程,JS 设计了事件循环机制,用来调度同步代码与异步代码的执行顺序。
一句话概括:
事件循环 = JS 异步非阻塞的底层实现。
二、核心概念:执行栈、宏任务、微任务
1. 执行栈(Call Stack)
同步代码直接放入执行栈,先进后出,立即执行。
2. 宏任务(Macrotask)
-
宿主环境提供(浏览器/Node.js)
-
每次事件循环只执行一个
-
常见宏任务:
- 全局 script 代码
setTimeout/setInterval- I/O 操作(ajax、文件读取)
- UI 渲染
3. 微任务(Microtask)
-
JS 引擎自身提供
-
执行栈为空时,一次性清空整个队列
-
优先级 远高于 宏任务
-
常见微任务:
Promise.then / catch / finallyawait后面的代码queueMicrotask()MutationObserver
三、事件循环执行规则(黄金定律)
这是做题、面试永远不会错的核心规则:
- 先执行所有同步代码
- 同步执行完毕 → 清空所有微任务
- 微任务清空 → 执行一个宏任务
- 宏任务执行完毕 → 再次清空微任务
- 循环往复
终极口诀:
同步优先 → 微任务插队 → 宏任务最后
四、面试高频坑点(90% 人踩过)
坑 1:Promise 内部是同步执行的
console.log(1);
new Promise((resolve) => {
console.log(2); // 同步执行!
resolve();
}).then(() => {
console.log(3); // 微任务
});
console.log(4);
// 输出:1 → 2 → 4 → 3
坑 2:await 后面的代码 = 微任务
async function fn() {
console.log(1);
await Promise.resolve();
console.log(2); // 微任务!
}
console.log(3);
fn();
console.log(4);
// 输出:3 → 1 → 4 → 2
坑 3:await 两种关键区别(大厂必考)
这是事件循环最难、最容易错的细节:
① await 后面是普通函数
async function fn1() {
console.log(1);
await fn2();
console.log(2);
}
✅ 微任务正常入队,先入队先执行。
② await 后面是 Promise
async function fn1() {
console.log(1);
await Promise.resolve();
console.log(2);
}
✅ 微任务延迟入队,比后面的 .then 更晚执行。
五、经典面试真题(带解析)
真题 1(基础)
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);
// 输出:1 → 4 → 3 → 2
真题 2(Promise 同步)
console.log('start');
new Promise((resolve) => {
console.log('promise');
resolve();
}).then(() => {
console.log('then');
});
console.log('end');
// 输出:start → promise → end → then
真题 3(async/await 综合题)
async function async1() {
console.log(1);
await async2();
console.log(2);
}
async function async2() {
console.log(3);
}
console.log(4);
async1();
new Promise(resolve => {
console.log(5);
resolve();
}).then(() => console.log(6));
console.log(7);
// 输出:4 → 1 → 3 → 5 → 7 → 2 → 6
真题 4(终极难度)
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => console.log(3));
}, 0);
Promise.resolve().then(() => {
console.log(4);
setTimeout(() => console.log(5), 0);
});
console.log(6);
// 输出:1 → 6 → 4 → 2 → 3 → 5
六、一页纸总结(建议收藏)
- JS 单线程,依靠事件循环实现异步非阻塞
- 执行顺序:同步 > 微任务 > 宏任务
- 微任务:一次性清空;宏任务:一次执行一个
- Promise 内部同步,
.then是微任务 await后面的代码一律是微任务await 普通函数:微任务正常排队await Promise:微任务延迟排队
写在最后
事件循环是前端基础的重中之重,不管是日常开发还是面试,都必须牢牢掌握。
把这篇文章多看两遍,配合真题练习,你完全可以轻松应对所有事件循环相关问题。
如果对你有帮助,欢迎 点赞 + 收藏 + 关注,后续持续输出前端硬核干货~