事件循环的新手指南

254 阅读4分钟

事件循环的解释

事件循环不仅仅是为JavaScript准备的。它是定义网络浏览器前端工作方式的标准。了解事件循环是创建伟大的基于网络的软件的第一步。

并行处理

在21世纪初,CPU制造商碰了壁。在当时,时钟周期是王道。你的CPU能产生的周期越多,它每秒能执行的指令就越多,机器就越快。随着循环次数的增加,热量也随之增加--太多的热量无法散去。

聪明的制造商转而选择增加内核的数量,允许追求性能的程序访问整个内核,将其余部分留给其他任务。聪明的程序员使用专门的语言来访问这些其他的核心,并挑出速度。如今,这些聪明的开发者将这些多核用于同步任务,如后台进程或同时运行两个应用程序。

JavaScript(JS)就没有那么聪明了。JS是一种单线程语言,意味着它只能访问一个核心来执行一行指令。这是一个很大的限制。例如,如果网站上有一个定时器,而另一个JS事件发生了,比如一个动画,那么定时器就会停止,而浏览器则处理其他指令。这种限制对JavaScript的未来发展是不利的。解决办法是什么?事件循环。

事件循环

事件循环不仅适用于JavaScript。它是定义网络浏览器前端工作方式的标准。

event loop 1

紫色的球代表事件循环的当前状态。就像一部电影,浏览器在每一帧都会重绘屏幕。在各帧之间,JavaScript运行。当脚本事件结束时,控制权被传回给浏览器,重新绘制发生的任何变化。这就使动画或用户体验元素保持流畅,同时仍在后台计算JavaScript。

堵塞排水口

在一个新的标签页中,试着把下面的代码粘贴到你的浏览器的控制台。之后你将不得不结束这个标签的进程。

while(true){ console.log("Section is cool") }

试着选择文本或点击按钮。不能吗?让我们看看为什么。

这里是我们代码运行时的事件循环。

它被卡在我们的JavaScript无限循环中。网站不能重新渲染或处理其他事件,如点击,因为我们还没有把控制权交还给浏览器。

这就是所谓的阻断事件循环。循环等长任务会无意中阻塞事件循环,并使网络应用和网站感到缓慢和无反应。

疏通下水道

幸运的是,我们有工具来解决这个问题。把这段代码复制到浏览器控制台。

let loop = setInterval(() => {
	console.log("Section is cool")
}, 0)

在这种情况下,我们使用setInterval(foo, 0) 。这将在回调队列中添加一个回调。像setInterval,setTimeout, 和promises这样的函数把代码放到队列中。当所有的任务结束时,JavaScript将以先入先出的结构开始运行队列中的代码。我们可以使用这些异步工具来编写非阻塞代码。

欢迎来到地狱

异步的JavaScript并不完美。一些JavaScript开发人员描述了回调地狱,当代码中包含许多函数,通常是嵌套的,这些函数将回调添加到回调队列中。这意味着有些函数没有从上到下解决,而是在稍后的时间被调用。以下面的代码为例。

function callbackFunction(){
	console.log("called foo")
}
foo(callbackFunction) //calls callbackFunction as async
console.log("finished script")

虽然看起来很无害,但这段代码有一些有趣的地方。由于提升(另一篇文章的主题),函数callbackFunction 被定义。然后我们调用foo 。这将 callbackFunction 放在回调栈上;"完成脚本 "现在被记录下来。现在堆栈是清空的,callbackFunction可以被调用并在日志中打印 "called foo"。我们最终得到的控制台日志看起来像是

finished script
called foo

这种非线性的执行方式很难理解,而且会导致回调地狱和未定义行为。

重要性

随着浏览器和JavaScript引擎变得更快、更强大,客户端的JavaScript变得更加高效。非阻塞的异步代码对于在运行期间使用网络/服务器请求或复杂进程的响应式网络应用程序来说至关重要。

了解事件循环是创建伟大的基于网络的软件的第一步。