跳到主要内容

事件循环

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();

执行过程:

  1. first() 被推入调用栈
  2. second() 被推入调用栈
  3. console.log('Second') 被执行并出栈
  4. second() 出栈
  5. console.log('First') 被执行并出栈
  6. first() 出栈

3. 异步任务和任务队列

当异步任务(如定时器、网络请求等)完成时,它们会将回调函数推入任务队列(Task Queue)。任务队列中的任务是按顺序执行的。

4. 微任务队列

微任务队列(Microtask Queue)是另一种任务队列,用于存放微任务。微任务优先级高于宏任务,会在每次事件循环的末尾立即执行。

常见的微任务包括:

  • Promise 回调
  • MutationObserver 回调

5. Event Loop 的工作原理

Event Loop 的主要职责是检查调用栈是否为空,如果为空,则从任务队列中取出第一个任务并执行。它会不断重复这个过程。

Event Loop 的步骤:

  1. 检查调用栈是否为空。
  2. 如果调用栈为空,从微任务队列中取出所有微任务并依次执行。
  3. 如果调用栈和微任务队列都为空,从任务队列中取出第一个任务并执行。
  4. 重复上述步骤。

6. 示例分析

console.log('Start');

setTimeout(() => {
console.log('Timeout');
}, 0);

Promise.resolve().then(() => {
console.log('Promise');
});

console.log('End');

执行过程:

  1. console.log('Start') 立即执行,输出 "Start"。
  2. setTimeout 将回调函数推入任务队列(0ms 后执行)。
  3. Promise.resolve().then 将回调函数推入微任务队列。
  4. console.log('End') 立即执行,输出 "End"。
  5. 调用栈清空,Event Loop 检查微任务队列,执行 Promise 回调,输出 "Promise"。
  6. 微任务队列清空,Event Loop 检查任务队列,执行 setTimeout 回调,输出 "Timeout"。

7. 浏览器与 Node.js 的 Event Loop

  • 浏览器:浏览器的 Event Loop 包含了多个任务队列,如宏任务队列和微任务队列。事件触发后回调函数进入相应的任务队列。
  • Node.js:Node.js 的 Event Loop 更复杂,包括六个阶段,每个阶段都有一个或多个任务队列。

总结

JavaScript 的 Event Loop 通过任务队列和微任务队列实现了异步编程模型,使得单线程的 JavaScript 能够处理并发任务,而不会阻塞主线程。理解 Event Loop 的工作机制是掌握 JavaScript 异步编程和性能优化的重要基础。