JavaScript 事件循环

摘要:在本教程中,您将了解 JavaScript 中的事件循环,以及 JavaScript 如何基于事件循环实现并发模型。

JavaScript 单线程模型

JavaScript 是一种单线程编程语言。这意味着 JavaScript 只能在单个时间点执行一项操作。

JavaScript 引擎从文件顶部开始执行脚本,并逐行向下执行。它创建 执行上下文,并在执行阶段将函数压入和弹出 调用堆栈

如果一个函数需要很长时间才能执行,您在函数执行期间就无法与 Web 浏览器进行交互,因为页面会卡住。

一个需要很长时间才能完成的函数称为阻塞函数。从技术上讲,阻塞函数会阻塞网页上的所有交互,例如鼠标点击。

一个阻塞函数的例子是调用远程服务器上的 API 的函数。

以下示例使用一个大循环来模拟阻塞函数

function task(message) {
    // emulate time consuming task
    let n = 10000000000;
    while (n > 0){
        n--;
    }
    console.log(message);
}

console.log('Start script...');
task('Call an API');
console.log('Done!');Code language: JavaScript (javascript)

在这个例子中,我们在 task() 函数中有一个大的 while 循环,它模拟一个耗时的任务。task() 函数是一个阻塞函数。

脚本会卡住几秒钟(取决于计算机的速度),并输出以下内容

Start script...
Download a file.
Done!

为了执行脚本,JavaScript 引擎将第一个调用 console.log() 放置在调用堆栈的顶部并执行它。然后,它将 task() 函数放在调用堆栈的顶部并执行该函数。

然而,完成 task() 函数需要一段时间。因此,您将在一段时间后看到消息 '下载文件。'。在 task() 函数完成后,JavaScript 引擎会将其从调用堆栈中弹出。

最后,JavaScript 引擎放置对 console.log('Done!') 函数的最后一次调用并执行它,这将非常快。

javascript event loop - callstack

回调函数来解救

为了防止阻塞函数阻塞其他活动,您通常将它放入 回调函数 以供稍后执行。例如

console.log('Start script...');

setTimeout(() => {
    task('Download a file.');
}, 1000);

console.log('Done!');Code language: JavaScript (javascript)

在这个例子中,您将立即看到消息 '开始脚本...''完成!'。之后,您将看到消息 '下载文件'

以下是输出

Start script...
Done!
Download a file.

如前所述,JavaScript 引擎一次只能执行一项操作。然而,更准确地说,JavaScript 运行时一次只能执行一项操作。

Web 浏览器还有其他组件,不仅仅是 JavaScript 引擎。

当您调用 setTimeout() 函数、发出 fetch 请求 或单击按钮时,Web 浏览器可以并发地和异步地执行这些活动。

setTimeout()、fetch 请求和 DOM 事件是 Web 浏览器 Web API 的一部分。

在我们的示例中,当调用 setTimeout() 函数时,JavaScript 引擎会将其放在调用堆栈中,而 Web API 会创建一个将在 1 秒后过期的计时器。

javascript event loop - step 1

然后 JavaScript 引擎将 task() 函数放入一个名为回调队列或任务队列的队列中

javascript event loop - step 2

事件循环是一个不断运行的进程,它监控回调队列和调用堆栈。

如果调用堆栈不为空,事件循环将等待它为空,然后将回调队列中的下一个函数放置到调用堆栈中。如果回调队列为空,则不会发生任何事情

javascript event loop - step 3

请看另一个例子

console.log('Hi!');

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

console.log('Bye!');
Code language: JavaScript (javascript)

在这个例子中,超时时间为 0 秒,因此消息 '立即执行。' 应该出现在消息 '再见!' 之前。然而,它并不像那样工作。

JavaScript 引擎将以下函数调用放置在回调队列中,并在调用堆栈为空时执行它。换句话说,JavaScript 引擎在 console.log('再见!') 之后执行它。

console.log('Execute immediately.');Code language: JavaScript (javascript)

以下是输出

Hi!
Bye!
Execute immediately.

下图说明了 JavaScript 运行时、Web API、调用堆栈和事件循环

javascript event loop

在本教程中,您已经了解了 JavaScript 事件循环,这是一个不断运行的进程,它协调调用堆栈和回调队列之间的任务,以实现并发。

本教程是否有帮助?