event loop 是JS 执行机制,也是代码执行的开始。
要理解 Event Loop,我们需要了解以下几个关键概念:
-
调用栈 (Call Stack) : 这是 JavaScript 引擎执行代码的地方。当一个函数被调用时,它会被推入调用栈。当函数执行完毕后,它会从调用栈中弹出。JavaScript 代码的同步执行过程就是在这个栈中进行的。
-
Web API (或 Node.js API) : 这些是浏览器(或 Node.js)提供的,用于处理异步任务的接口。例如:
setTimeout()
和setInterval()
用于定时器fetch()
和XMLHttpRequest
用于网络请求- DOM 事件监听器(如
addEventListener
)
当 JavaScript 代码调用这些 API 时,它们会将相应的任务交给浏览器或 Node.js 的底层线程去处理,而不会阻塞 JavaScript 主线程。
-
同步任务(Synchronous Tasks)和异步任务(Asynchronous Tasks) : 同步任务 是指那些在主线程上按顺序执行 的任务。 异步任务 是指那些耗时性的任务。它们不会阻塞主线程。
-
任务队列 (Task Queue / Callback Queue) : 当 Web API(或 Node.js API)处理完异步任务后,会将相应的回调函数放入一个队列中。这个队列通常被称为宏任务队列 (MacroTask Queue) 。常见的宏任务包括:
setTimeout
、setInterval
- DOM 事件
- I/O 操作
MessageChannel
setImmediate
(Node.js 专属)
-
微任务队列 (MicroTask Queue) : 这是一个优先级更高的队列,用于存放微任务 (MicroTasks) 。微任务在宏任务执行完之后、下一个宏任务开始之前执行。常见的微任务包括:
Promise
的then()
、catch()
、finally()
MutationObserver
queueMicrotask
Event Loop 的运行机制
Event Loop 的工作流程可以概括为以下步骤:
- 执行主线程同步代码:当 JavaScript 代码开始执行时,所有同步代码会被推入调用栈,并立即执行(JavaScript 代码执行相当于一个宏任务开始)。
- 清空调用栈:当调用栈中的所有同步代码执行完毕,调用栈变空。
- 检查微任务队列:Event Loop 会首先检查微任务队列。如果微任务队列中有任务,它会清空所有微任务,将它们依次推入调用栈执行,直到微任务队列为空。
- 检查宏任务队列 :在清空微任务队列后,Event Loop 会从宏任务队列中取出一个(注意:通常是一个)任务,将其对应的回调函数推入调用栈执行。
- 循环往复:当这个宏任务执行完毕,调用栈再次变空后,Event Loop 会回到步骤 3,再次检查微任务队列,然后是宏任务队列,如此循环往复,直到所有任务都被处理。

宏任务与微任务的优先级
理解宏任务和微任务的执行顺序是 Event Loop 的关键。总结来说:
- 同步代码最先执行。
- 微任务具有更高的优先级,它们会在当前宏任务执行完毕后立即执行,即使有新的宏任务已经准备好。
- 每执行完一个宏任务,都会检查并清空微任务队列。
常见的宏任务和微任务
宏任务
- script (整体代码):这是最开始执行的宏任务。
- setTimeout() 和 setInterval():定时器回调函数。
- setImmediate() (Node.js):在 I/O 事件之后,但在 setTimeout 和 setInterval 之前执行。
- I/O (输入/输出):例如文件读写、网络请求等。
- UI 渲染 (浏览器环境):页面的重绘和重排。
- MessageChannel (浏览器和 Node.js):用于不同事件循环之间的通信。
微任务
- Promise 的 then()、catch()、finally() 回调:Promise 状态改变后执行的回调。
- process.nextTick() (Node.js):这是 Node.js 特有的,它比 Promise 回调的优先级更高,会在当前操作的末尾,下一个事件循环开始前执行。
- MutationObserver (浏览器环境):用于监听 DOM 树变化的 API。
- queueMicrotask():一个可以直接将函数添加到微任务队列的 API。