JavaScript 执行机制与事件循环详解(面试必备)
面试系列要不都用这个配图?
目录
- 1. 为什么要理解事件循环?
- 2. JavaScript 单线程与任务模型
- 3. 宏任务与微任务
- 4. asyncawait 的本质
- 5. 常见面试题解析
- 6. 浏览器 vs Node.js
- 7. 面试回答思路(拿满分)
- 8. 总结
1. 为什么要理解事件循环?
前端面试中,事件循环(Event Loop) 是必考点。来看一道经典题:
console.log('start')
setTimeout(() => console.log('timeout'))
Promise.resolve().then(() => console.log('promise'))
console.log('end')
👉 输出顺序是:
start
end
promise
timeout
✅ 面试官常考这个题,考察的就是你是否理解 JavaScript 的执行机制。
2. JavaScript 单线程与任务模型
JavaScript 设计初衷是运行在浏览器中操作 DOM。由于 DOM 不能并发修改,所以 JavaScript 采用单线程模型。
- 同步任务:直接进入 调用栈(Call Stack) 执行。
- 异步任务:完成后进入 任务队列(Task Queue),由事件循环调度。
3. 宏任务与微任务
异步任务分为两类:
宏任务(Macro Task)
- 整体 script 代码
setTimeout / setInterval
- I/O 操作
- UI 渲染
- (Node.js 中还包括
setImmediate
,浏览器还可用MessageChannel
)
微任务(Micro Task)
Promise.then / catch / finally
queueMicrotask
MutationObserver
执行顺序规则
- 执行一个宏任务(例如整个 script)。
- 清空当前产生的所有微任务。
- 浏览器可能渲染一次 UI(取决于刷新频率)。
- 执行下一个宏任务。
4. async/await 的本质
async/await
是 Promise 的语法糖。
示例:
async function test() {
console.log(1)
await Promise.resolve()
console.log(2)
}
test()
console.log(3)
输出结果:
1
3
2
✅ 解析:
await
会把后续语句(console.log(2)
)封装成 微任务;- 当前宏任务执行到
console.log(3)
后,事件循环才会调度这个微任务。
5. 常见面试题解析
题目 1:Promise vs setTimeout
console.log('start')
setTimeout(() => console.log('timeout'))
Promise.resolve().then(() => console.log('promise'))
console.log('end')
输出:
start
end
promise
timeout
✅ 原因:
script
是宏任务 → 依次执行start → 注册 setTimeout → 注册 Promise → end
- 宏任务结束 → 清空微任务 → 输出
promise
- 下一轮宏任务 → 输出
timeout
题目 2:async/await + setTimeout
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(() => console.log('setTimeout'), 0)
async1()
new Promise(resolve => {
console.log('promise1')
resolve()
}).then(() => console.log('promise2'))
console.log('script end')
输出:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
✅ 解析:
- 同步阶段:
script start → async1 start → async2 → promise1 → script end
- 微任务:
async1 end, promise2
- 宏任务:
setTimeout
题目 3:多层 Promise.then
Promise.resolve()
.then(() => {
console.log('p1')
return Promise.resolve()
})
.then(() => {
console.log('p2')
})
输出:
p1
p2
✅ 原因:
- 第一个
then
注册微任务 → 输出p1
- 返回的
Promise.resolve()
立即 resolved → 再注册微任务 → 输出p2
6. 浏览器 vs Node.js
浏览器事件循环:宏任务 ↔ 微任务交替。
Node.js 事件循环(基于 libuv):阶段顺序 → timers → pending callbacks → idle/prepare → poll → check → close callbacks
且在 每个阶段结束后都会执行微任务。
👉 面试加分点:明确指出浏览器与 Node.js 执行顺序的差异。
7. 面试回答思路(拿满分)
如果被问到 “能解释一下 JS 事件循环吗?”,可以这样答:
- JavaScript 是单线程,需要事件循环来处理异步任务。
- 异步任务分为 宏任务 和 微任务。
- 执行顺序:宏任务 → 清空微任务 → (可能渲染) → 下一个宏任务。
- 举例说明:
Promise vs setTimeout
。 - 补充:
async/await
的本质,以及 Node.js 的执行差异。
✅ 这样回答既有逻辑,又有深度。
8. 总结
- JavaScript 是单线程语言。
- 异步任务分为 宏任务 和 微任务。
- 执行顺序:宏任务 → 清空微任务 → 渲染 → 下一个宏任务。
- 面试高频考点:
- Promise vs setTimeout
- async/await
- Node.js 与浏览器的差异
👉 一句话记忆:“一个宏任务结束后,会立刻清空所有微任务,再进入下一个宏任务。”