事件循环
JavaScript 中的 Event Loop 是一种处理并发的机制,它允许 JavaScript 进行非阻塞异步编程,尽管 JavaScript 是单线程的。理解 Event Loop 是掌握 JavaScript 异步行为和性能优化的关键。以下是对 JavaScript 中 Event Loop 的详细解释
1. JavaScript 的单线程模型
JavaScript 在浏览器环境和 Node.js 中都是单线程的,这意味着同一时间只能执行一个任务。然而,JavaScript 可以处理异步操作,比如 I/O 操作、定时器、网络请求等,而不会阻塞主线程。这是通过 Event Loop 实现的。
2. 执行上下文和调用栈
当 JavaScript 代码执行时,会形成一个执行上下文(Execution Context),并被推入调用栈(Call Stack)。调用栈是一个 LIFO(后进先出)结构,用于跟踪当前的执行位置和函数调用。
示例:
function first() {
second();
console.log('First');
}
function second() {
console.log('Second');
}
first();
执行过程:
first()
被推入调用栈second()
被推入调用栈console.log('Second')
被执行并出栈second()
出栈console.log('First')
被执行并出栈first()
出栈
3. 异步任务和任务队列
当异步任务(如定时器、网络请求等)完成时,它们会将回调函数推入任务队列(Task Queue)。任务队列中的任务是按顺序执行的。
4. 微任务队列
微任务队列(Microtask Queue)是另一种任务队列,用于存放微任务。微任务优先级高于宏任务,会在每次事件循环的末尾立即执行。
常见的微任务包括:
- Promise 回调
- MutationObserver 回调
5. Event Loop 的工作原理
Event Loop 的主要职责是检查调用栈是否为空,如果为空,则从任务队列中取出第一个任务并执行。它会不断重复这个过程。
Event Loop 的步骤:
- 检查调用栈是否为空。
- 如果调用栈为空,从微任务队列中取出所有微任务并依次执行。
- 如果调用栈和微任务队列都为空,从任务队列中取出第一个任务并执行。
- 重复上述步骤。
6. 示例分析
console.log('Start');
setTimeout(() => {
console.log('Timeout');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
console.log('End');
执行过程:
console.log('Start')
立即执行,输出 "Start"。setTimeout
将回调函数推入任务队列(0ms 后执行)。Promise.resolve().then
将回调函数推入微任务队列。console.log('End')
立即执行,输出 "End"。- 调用栈清空,Event Loop 检查微任务队列,执行
Promise
回调,输出 "Promise"。 - 微任务队列清空,Event Loop 检查任务队列,执行
setTimeout
回调,输出 "Timeout"。
7. 浏览器与 Node.js 的 Event Loop
- 浏览器:浏览器的 Event Loop 包含了多个任务队列,如宏任务队列和微任务队列。事件触发后回调函数进入相应的任务队列。
- Node.js:Node.js 的 Event Loop 更复杂,包括六个阶段,每个阶段都有一个或多个任务队列。
总结
JavaScript 的 Event Loop 通过任务队列和微任务队列实现了异步编程模型,使得单线程的 JavaScript 能够处理并发任务,而不会阻塞主线程。理解 Event Loop 的工作机制是掌握 JavaScript 异步编程和性能优化的重要基础。