返回

JavaScript 事件循环:抽丝剥茧,轻松掌握运行机制

前端

JavaScript 是一门单线程语言,这意味着它一次只能执行一个任务。为了协调不同任务的执行顺序,JavaScript 引入了事件循环(EventLoop)的概念。事件循环是一个不断循环的过程,它从事件队列中获取事件,然后将这些事件放入调用栈中执行。

在事件循环中,有两个重要的队列:事件队列和调用栈。事件队列是一个先进先出的队列,当事件发生时,它会被放入事件队列中。调用栈是一个后进先出的栈,当一个事件被执行时,它会被压入调用栈中。

当事件循环开始时,它会检查事件队列中是否有事件。如果有事件,它会将事件从事件队列中取出,并将其放入调用栈中执行。当调用栈中的事件执行完毕后,它会被弹出调用栈。然后,事件循环会再次检查事件队列中是否有事件,如果有,它会将事件从事件队列中取出,并将其放入调用栈中执行。如此循环往复,直到事件队列中没有更多事件。

除了事件队列和调用栈外,JavaScript 还引入了微任务队列(Microtask Queue)的概念。微任务队列是一个先进先出的队列,当一个微任务被创建时,它会被放入微任务队列中。当调用栈中的事件执行完毕后,事件循环会检查微任务队列中是否有微任务。如果有微任务,它会将微任务从微任务队列中取出,并将其放入调用栈中执行。

JavaScript 的事件循环是一个复杂的机制,但它也是一个非常重要的机制。理解事件循环可以帮助我们更好地理解 JavaScript 的运行机制,并编写出更加高效和健壮的 JavaScript 代码。

下面,我们通过一些具体的示例来演示 JavaScript 的事件循环是如何工作的。

示例 1:

console.log('1');
setTimeout(() => {
  console.log('2');
}, 0);
console.log('3');

在示例 1 中,我们首先在控制台中输出了一个数字 1。然后,我们使用 setTimeout() 函数创建了一个定时器,并在定时器中输出了一个数字 2。最后,我们在控制台中输出了一个数字 3。

当我们运行这段代码时,首先会执行 console.log('1') 语句,然后会将 setTimeout() 函数回调放入事件队列中。然后,事件循环会检查事件队列中是否有事件,发现有事件,于是将 setTimeout() 函数回调从事件队列中取出,并将其放入调用栈中执行。

当 setTimeout() 函数回调执行完毕后,它会被弹出调用栈。然后,事件循环会再次检查事件队列中是否有事件,发现没有事件,于是开始执行 console.log('3') 语句。

示例 2:

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

在示例 2 中,我们首先在控制台中输出了一个数字 1。然后,我们使用 Promise.resolve() 函数创建了一个 Promise 对象,并在 then() 方法中输出了一个数字 2。最后,我们在控制台中输出了一个数字 3。

当我们运行这段代码时,首先会执行 console.log('1') 语句,然后会将 Promise.resolve() 函数回调放入微任务队列中。然后,事件循环会检查事件队列中是否有事件,发现没有事件,于是开始执行 console.log('3') 语句。

当 console.log('3') 语句执行完毕后,事件循环会检查微任务队列中是否有微任务,发现有微任务,于是将 Promise.resolve() 函数回调从微任务队列中取出,并将其放入调用栈中执行。

当 Promise.resolve() 函数回调执行完毕后,它会被弹出调用栈。然后,事件循环会再次检查事件队列和微任务队列中是否有事件或微任务,发现没有,于是退出循环。

通过这两个示例,我们可以看到 JavaScript 的事件循环是如何工作的。事件循环是一个不断循环的过程,它从事件队列中获取事件,然后将这些事件放入调用栈中执行。当调用栈中的事件执行完毕后,它会被弹出调用栈。然后,事件循环会再次检查事件队列和微任务队列中是否有事件或微任务,如果有,它会将事件或微任务从队列中取出,并将其放入调用栈中执行。如此循环往复,直到事件队列和微任务队列中没有更多事件或微任务。