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