事件循环(Event Loop)机制?

0 阅读3分钟

一、为什么需要 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 LoopCall 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