JavaScript 的事件循环机制(Event Loop)是其运行时环境中的核心概念之一,用于处理异步操作和协调代码执行顺序。掌握事件循环对于理解 JavaScript 的异步编程至关重要。
JavaScript 的运行时环境
JavaScript 是一种单线程语言,意味着它在同一时刻只能执行一个任务。然而,JavaScript 的运行时环境(如浏览器或 Node.js)提供了一些机制来支持异步操作,而事件循环正是关键机制之一。
事件循环的基本概念
事件循环的核心目标是确保 JavaScript 程序能够高效地处理异步任务,同时避免阻塞主线程。它通过协调以下三个主要组件来实现这一目标:
调用栈(Call Stack)
:用于存储当前正在执行的函数,采用 LIFO(后进先出
)原则。任务队列(Task Queue)
:存储待执行的异步任务。事件循环(Event Loop)
:监控调用栈和任务队列,确保任务按顺序执行。
事件循环的工作原理
事件循环的工作原理可以概括为以下步骤:
执行同步代码(主线程代码)。
检查调用栈是否为空:如果调用栈为空,事件循环会从任务队列中取出任务执行。
执行微任务队列中的任务(如 Promise 回调)。
执行宏任务队列中的任务(如 setTimeout 回调)。
重复上述过程,直到所有任务执行完毕。
任务的分类
在 JavaScript 中,任务主要分为两类:
宏任务(Macrotask)
:包括setTimeout
、setInterval
、setImmediate
(Node.js)、I/O 任务、UI 渲染等。微任务(Microtask)
:包括Promise.then
、MutationObserver
、process.nextTick
(Node.js)。注意:Promise 本身是一个同步函数,但它的回调(then、catch、finally)是异步执行的
console.log("Start") const promise = new Promise((resolve, reject) => { console.log("Inside Promise") resolve("Resolved") }) promise.then((res) => { console.log(res) }) console.log("End") // 输出: Start Inside Promise End Resolved
宏任务与微任务的执行顺序
事件循环在执行任务时,会优先处理微任务队列中的任务。具体执行顺序如下:
执行当前宏任务(如 setTimeout 的回调)。
执行微任务队列中的所有任务(如 Promise.then)。
处理下一个宏任务,并重复以上过程。
示例代码
以下是一个示例,展示事件循环如何处理宏任务和微任务:
console.log("1")
setTimeout(() => {
console.log("2")
}, 0)
Promise.resolve().then(() => {
console.log("3")
})
console.log("4")
执行过程分析
console.log(“1”) 是同步代码,立即执行,输出 1。
setTimeout 是宏任务,回调函数被放入宏任务队列。
Promise.then 是微任务,回调函数被放入微任务队列。
console.log(“4”) 是同步代码,立即执行,输出 4。
执行微任务 console.log(“3”),输出 3。
执行宏任务 console.log(“2”),输出 2。
最终输出顺序
1
4
3
2
Node.js 事件循环的不同之处
在 Node.js 中,事件循环与浏览器环境略有不同,特别是 process.nextTick 和 setImmediate 的处理方式:
console.log("A")
setTimeout(() => console.log("B"), 0)
process.nextTick(() => console.log("C"))
Promise.resolve().then(() => console.log("D"))
setImmediate(() => console.log("E"))
console.log("F")
执行顺序
同步代码:
A
、F
。process.nextTick(优先级最高):
C
。Promise.then:
D
。宏任务队列(setTimeout、setImmediate):
setTimeout("B")
。setImmediate("E")
。
最终输出顺序
A
F
C
D
B
E
事件循环的意义
事件循环机制使得 JavaScript 能够在单线程环境下高效地处理异步任务,避免了线程阻塞问题,同时保证了代码的执行顺序和逻辑的正确性。
总结
事件循环:协调调用栈和任务队列,确保任务按顺序执行。
宏任务和微任务:宏任务包括 setTimeout 等,微任务包括 Promise.then 等。
执行顺序:优先执行同步代码 -> 执行微任务 -> 执行宏任务。