摘要:在本教程中,您将了解 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!')
函数的最后一次调用并执行它,这将非常快。

回调函数来解救
为了防止阻塞函数阻塞其他活动,您通常将它放入 回调函数 以供稍后执行。例如
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 引擎将 task()
函数放入一个名为回调队列或任务队列的队列中

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

请看另一个例子
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 事件循环,这是一个不断运行的进程,它协调调用堆栈和回调队列之间的任务,以实现并发。