摘要:在本教程中,您将了解 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 事件循环,这是一个不断运行的进程,它协调调用堆栈和回调队列之间的任务,以实现并发。