事件循环(Event Loop) - 前端面试核心题
这是前端面试最重要、最核心、最容易区分候选人水平的题之一。
0. 面试"标准一句话"版本
JS 是单线程语言,事件循环(Event Loop)负责协调同步任务、微任务(Promise)、宏任务(setTimeout等)的执行顺序;同步任务优先执行,之后每一轮循环都会先清空微任务队列,再执行一个宏任务,如此循环。
如果一分钟要答完,这样非常稳。
1. 为什么需要事件循环?(面试官很爱问)
因为:
- JS 是单线程
- 但浏览器要同时处理 UI 渲染、定时器、网络、用户输入等
所以必须用一个调度机制来"异步执行任务" → 这就是事件循环(Event Loop)
你的回答要提到"避免阻塞 UI" = 加分。
2. 事件循环的流程(核心逻辑)
事件循环的本质是:
同步任务 → 微任务 → 宏任务 → 微任务 → 宏任务 → ...
通常流程:
- 执行同步任务(栈)
- 清空所有微任务队列(microtask queue)
- 执行一个宏任务(macrotask queue)
- 再次清空微任务
- 再执行下一个宏任务
- 无限循环(Loop)
3. 宏任务 vs 微任务(必须背)
微任务(microtasks)
Promise.then/catch/finallyMutationObserverqueueMicrotask()- (Node)
process.nextTick
宏任务(macrotasks)
setTimeoutsetIntervalsetImmediate(Node)- I/O 回调
- UI 渲染事件
MessageChannel
口诀:微任务优先,Promise 是微任务。
4. 最经典的事件循环例题(90% 的面试官会问)
题 A:
javascript
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
console.log(4);
执行顺序:
1
4
3
2
解释:
- 同步:1 → 4
- 微任务(then):3
- 宏任务(setTimeout):2
题 B:Promise 中的多层微任务
javascript
Promise.resolve()
.then(() => {
console.log(1);
Promise.resolve().then(() => console.log(2));
})
.then(() => console.log(3));
输出顺序:
1
2
3
解释: 每执行完一个 then,产生的微任务会加入同一轮的微任务队列中。
题 C:超级经典陷阱(setTimeout vs Promise)
javascript
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
输出仍然是:
1
4
3
2
哪怕 setTimeout 是 0,也永远比 Microtask 晚。
5. Event Loop 与浏览器渲染(加分项)
每一轮宏任务结束后,浏览器通常会进行:
- 页面重绘(paint)
- 布局计算(layout)
- 样式计算(recalculate style)
所以: 微任务多了,会导致 UI 渲染被推迟(例如疯狂递归 Promise.then 会卡页面)
这是高级候选人的加分点。
6. Node.js 的事件循环(面试进阶)
你至少要答出:
Node 有自己的事件循环,与浏览器不同,基于 libuv,有 6 个阶段,每个阶段都有自己的队列;process.nextTick 优先级最高,属于独立队列。
面试官会觉得你"懂事件循环的底层原理"。
7. 面试优化版答题结构(满分答案)
- JS 是单线程,需要异步 → Event Loop 负责调度
- 同步任务先执行
- 之后清空微任务(Promise)
- 再执行一个宏任务(setTimeout)
- 每次宏任务执行后都会再清空一次微任务
- 微任务优先于宏任务
- Promise.then 是微任务;setTimeout 是宏任务
- Node.js 和浏览器略有不同(加分)
8. 前端事件循环的"核心口诀"
把这些背下来,任何场景都不会错。
- 口诀 1: 同步先执行,Promise 比 setTimeout 快
- 口诀 2: 每一轮宏任务后,必须清空微任务
- 口诀 3: 0 秒定时器 ≠ 立即执行
- 口诀 4: Promise 的 then 是微任务,await 也是微任务
- 口诀 5: 微任务塞太多会卡 UI
9. 练习题(面试模拟)
练习 1:简单版
javascript
setTimeout(() => console.log(1));
Promise.resolve().then(() => console.log(2));
console.log(3);
顺序: 3、2、1
练习 2:进阶版
javascript
console.log(1);
setTimeout(() => {
console.log(2);
Promise.resolve().then(() => console.log(3));
});
Promise.resolve().then(() => {
console.log(4);
});
console.log(5);
顺序:
1
5
4
2
3
练习 3:最经典"await"题
javascript
async function test() {
console.log(1);
await console.log(2);
console.log(3);
}
test();
console.log(4);
输出:
1
2
4
3
await 把后面的部分拆成微任务。