在本教程中,您将了解 JavaScript 中的事件轮询以及 JavaScript 如何基于事件轮询实现并发模型。

JavaScript 单线程模型

JavaScript 是一种单线程编程语言。这意味着 JavaScript 在一个时间点只能做一件事。

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

如果一个函数需要很长时间才能完成执行,那么在函数执行期间您将无法与 Web 浏览器交互,因为页面会挂起。

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

阻塞函数的一个示例是从远程服务器调用 API 的函数。下面的例子使用一个大循环来模拟阻塞函数:

function task(message) {
    // 模拟消耗大量时间的任务
    let n = 10000000000;
    while (n > 0){
        n--;
    }
    console.log(message);
}

console.log('Start script...');
task('Call an API');
console.log('Done!');

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

脚本挂起几秒钟并打印以下输出到控制台:

Start script...
Download a file.
Done!

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

但是,完成 task() 函数需要一段时间。因此,稍后您会看到消息 'Download a file.'task() 函数完成后,JavaScript 引擎将其从调用栈弹出 task() 函数。

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

事件轮询与回调

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

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

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

console.log('Done!');

在此示例中,您将立即看到 'Start script...''Done!' 消息 。之后,您会看到消息'Download a file'

这是输出:

Start script...
Done!
Download a file.

如前所述,JavaScript 引擎一次只能做一件事。但是,更准确地说,JavaScript 运行时一次只能做一件事。

Web 浏览器还有其他组件,而不仅仅是 JavaScript 引擎。当您调用 setTimeout() 函数、发出 Fetch 请求或单​​击按钮时,Web 浏览器可以并发和异步地执行这些活动。

Fetch 请求 ,setTimeout()DOM 事件是浏览器 Web API 的一部分。

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

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

事件轮询是一个不断运行的进程,它同时监听回调队列和调用栈。如果调用栈不为空,则事件轮询等待直到它为空,然后将回调队列中的下一个函数放入调用栈。如果回调队列为空,则什么也不会发生:

看另一个例子:

console.log('Hi!');

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

console.log('Bye!');

在此示例中,超时为 0 秒,因此消息 'Execute immediately.' 应出现在消息 'Bye!' 之前 。但是,它不是那样工作的。

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

console.log('Execute immediately.');

这是输出:

Hi!
Bye!
Execute immediately.

下图说明 JavaScript 运行时、Web API、调用堆栈和事件轮询:

结论

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