一、为什么需要 Event Loop
JavaScript 是 单线程语言。
意思是:
同一时间只能执行一个任务
但现实中有很多 异步操作:
- 定时器
setTimeout - 网络请求
fetch - DOM 事件
- Promise
如果 JS 等这些操作完成再继续执行,页面就会 卡死。
所以浏览器设计了一套机制:
同步任务先执行
异步任务排队
循环检查队列执行
这套机制就是 事件循环(Event Loop) 。
二、Event Loop 整体结构
浏览器运行 JS 时主要有几个部分:
Call Stack(调用栈)
Web APIs(浏览器API)
Task Queue(任务队列)
Event Loop(事件循环)
执行流程:
1 执行同步代码
2 遇到异步任务交给 Web API
3 异步完成进入任务队列
4 Event Loop 检查调用栈
5 调用栈为空 → 执行队列任务
流程图:
Call Stack
↓
执行同步代码
↓
异步任务 → Web APIs
↓
任务完成 → Task Queue
↓
Event Loop
↓
Call Stack 为空 → 执行任务
三、任务类型
事件循环中任务分两种:
宏任务(Macro Task)
微任务(Micro Task)
四、宏任务(Macro Task)
常见宏任务:
setTimeout
setInterval
setImmediate(Node)
I/O
script
UI渲染
例子:
setTimeout(() => {
console.log("timeout")
}, 0)
五、微任务(Micro Task)
微任务优先级 高于宏任务。
常见微任务:
Promise.then
Promise.catch
Promise.finally
MutationObserver
queueMicrotask
process.nextTick(Node)
例子:
Promise.resolve().then(() => {
console.log("promise")
})
六、执行顺序(核心)
事件循环每次执行顺序:
1 执行同步代码
2 执行所有微任务
3 执行一个宏任务
4 再执行所有微任务
5 重复循环
总结口诀:
同步 → 微任务 → 宏任务
七、经典面试题
看代码:
console.log("1")
setTimeout(() => {
console.log("2")
}, 0)
Promise.resolve().then(() => {
console.log("3")
})
console.log("4")
执行顺序?
第一步:同步代码
1
4
第二步:微任务
3
第三步:宏任务
2
最终结果:
1
4
3
2
八、复杂一点的面试题
console.log("start")
setTimeout(() => {
console.log("timeout")
})
Promise.resolve().then(() => {
console.log("promise1")
}).then(() => {
console.log("promise2")
})
console.log("end")
执行顺序:
同步:
start
end
微任务:
promise1
promise2
宏任务:
timeout
最终:
start
end
promise1
promise2
timeout
九、async / await 和 Event Loop
await 本质也是 微任务。
例子:
async function test() {
console.log(1)
await Promise.resolve()
console.log(2)
}
test()
console.log(3)
执行顺序:
同步:
1
3
微任务:
2
最终:
1
3
2
十、浏览器 Event Loop 完整流程
一次循环:
1 执行一个宏任务
2 执行所有微任务
3 更新渲染
4 执行下一个宏任务
图示:
Macro Task
↓
Micro Tasks
↓
Render
↓
Next Loop
十一、为什么 Promise 比 setTimeout 先执行
因为:
Promise → 微任务
setTimeout → 宏任务
微任务优先级更高。
十二、面试标准回答
可以这样说:
JavaScript 是单线程语言,为了处理异步任务,浏览器引入了事件循环机制。
当代码执行时,同步任务会进入调用栈执行,异步任务会交给浏览器 API 处理。
当异步任务完成后,会进入任务队列等待执行。
事件循环会不断检查调用栈是否为空,如果为空就从任务队列取任务执行。
任务分为宏任务和微任务,执行顺序是:同步代码 → 微任务 → 宏任务。
Promise.then 属于微任务,而 setTimeout 属于宏任务,所以 Promise 会先执行。
十三、一句话总结
JS执行顺序:
同步代码
↓
微任务队列
↓
宏任务队列
↓
循环执行(Event Loop)