JavaScript 中的 Event Loop
Event Loop 是什么
Event Loop 是 JavaScript 实现异步的核心机制,解决 JavaScript 单线程带来的运行阻塞的问题。
JavaScript 之所以是单线程,是因为最初被用来设计处理网页交互时,需要操作 DOM,如果是多线程,一个线程正在修改 DOM,另一个线程也在修改同一个 DOM,会导致冲突和复杂的并发问题。
宏任务和微任务
首先看下 HTML 规范,从中可得知关于 Event Loop 的几个要点:
- task queue 宏任务队列

每个 event loop 中有一个或多个 task queue,task queue 是一个集合而不是队列。
这里说 task queue 是一个集合(set),而不是队列(list),是因为 event loop 会从 task queue 中抓取第一个可运行的任务,而不是按照队列顺序一个个地出队。
- microtask queue 微任务队列

每个 event loop 有一个 microtask queue,用于存放微任务,microtask 不是 task queue
在浏览器中,常见的宏任务、微任务如下:
宏任务
setTimeoutsetIntervalsetImmediateI/O事件MessageChannel
微任务
Promise.then/catch/finallyprocess.nextTick(Node.js 环境,优先级高于 Promise)MutationObserver(监视 DOM 变化)queueMicroTask(直接创建微任务的方法)
一图了解 Event Loop
了解完宏任务、微任务,我们可以看下这张图,在 JavaScript 实际运行的过程中,代码被一行行执行,任务会被加入到执行栈中(call stack),执行完毕后从栈中弹出。JavaScript 提供的 WebAPIs (例如 setTimeout、DOM、fetch 等)方法会将它们的回调添加到 task queue 中,等待执行。queueMicrotask、Promise、MutationObserver 等方法会创建微任务,添加到 microtask queue 中等待执行。

Event Loop 事件循环在其中负责这几件事:
- 监控调用栈(call stack)
- 管理任务队列(task queue/microtask queue)
Event Loop 事件循环机制保证 JavaScript 能够在单线程环境中有效地进行异步编程,从而提高应用程序的响应性和性能,让我们来看看它的机制具体是怎样的。
Event Loop 执行流程
Event Loop 的执行流程可以分为以下几个步骤:
-
执行同步代码
执行当前宏任务中的所有同步代码
-
清空微任务队列
当同步代码执行完毕后,立即检查微任务队列
执行所有微任务,直到清空微任务队列
如果在清空微任务队列时,产生了新的微任务,也会立即执行
-
UI 渲染
微任务队列清空后,检查是否需要 UI 渲染,如果需要就进行 UI 渲染
-
执行下一个宏任务
从宏任务队列中取出一个任务执行,重复2-4的步骤
例子
理解 Event Loop 最好的方法就是实践,让我们来举个例子,下面这段代码会如何执行:
console.log('start')
setTimeout(() => {
console.log('setTimeout')
}, 0)
Promise.resolve().then(() => {
console.log('promise1')
}).then(() => {
console.log('promise2')
})
console.log('end')运行结果:
start
end
promise1
promise2
setTimeout执行流程分析:
- print
start - 执行
setTimeout,将回调函数注册到宏任务队列 - 执行
Promise,将回调函数注册到微任务队列 - print
end - Call Stack 清空
- 执行所有微任务,print
promise1、promise2,微任务队列清空 - 取宏任务队列中的第一个执行,print
setTimeout