事件循环是用来理解JavaScript的最重要的方面之一。这篇文章旨在解释JavaScript如何与单个线程一起工作的细节,以及它如何处理异步函数。
JavaScript代码运行是单线程。一次只执行一件事。这实际上是一个非常有用的限制,因为它简化了很多程序,从而不必担心并发问题。
您只需要注意编写代码的方式,避免任何可能阻塞线程的内容,如同步调用或无限循环。
通常,在大多数浏览器中,每个浏览器都有一个事件循环,以使每个进程隔离,并避免web页面具有无限循环或繁重的处理来阻塞整个浏览器。
你最需要担心的是,您的代码将在单个事件循环上运行,并在编写代码时考虑到这一点,以避免阻塞它。
阻止事件循环
任何花费太长时间将控制权返回给事件循环的JavaScript代码都会阻止页面中任何JavaScript代码的执行,甚至阻止UI线程,用户也无法点击,滚动页面等等。
几乎所有JavaScript中的I / O操作都是非阻塞的。网络请求,Node.js文件系统操作等。阻塞是个例外,这就是为什么JavaScript基于回调,以及最近的promises和async / await。
调用堆栈
调用堆栈是LIFO队列(Last In,FirstOut)。事件循环不断检查调用堆栈以查看是否存在需要运行的任何函数。
在执行此操作时,它会将它找到的任何函数调用添加到调用堆栈并按顺序执行每个调用。
一个简单的事件循环说明:
当此代码运行时,首先foo()调用。在foo()我们第一次调用bar(),然后我们调用baz()。
排队功能执行
上面的例子运行特点:JavaScript找到要执行的东西,按顺序运行它们。
让我们看看如何推迟函数直到堆栈清除:
用例setTimeout(()=> {}), 0)是调用一个函数,但是一旦执行了代码中的每个其他函数就执行它。
当此代码运行时,首先调用foo()。在foo()里面我们首先调用setTimeout,bar作为参数传递,然后我们指示它尽可能快地运行,将0作为计时器传递。然后我们调用baz()。
消息队列
调用setTimeout()时,浏览器或Node.js启动计时器。当计时器到期,我们将0作为超时,回调函数立即被放入消息队列中。
消息队列也是用户发起的事件(如单击事件、键盘事件或获取响应)在代码有机会对其作出响应之前排队的地方。或者像onLoad这样的DOM事件。
循环优先处理调用堆栈,它首先处理在调用堆栈中找到的所有东西,一旦调用堆栈中没有任何东西,它就会去获取事件队列中的东西。
我们不必等待像setTimeout,fetch或其他东西这样的函数来完成自己的工作,因为它们是由浏览器提供的,并且它们运行在自己的线程中。
ES6作业队列
ECMAScript 2015引入了Promises使用的作业队列概念(也在ES6 / ES2015中引入)。这是一种尽快执行异步函数结果的方法,而不是放在调用堆栈的末尾。
在当前函数结束之前解析的Prom将在当前函数之后立即执行。
我觉得在游乐园里过山车的比喻很好:消息队列将你放在队列的后面,在所有其他人的后面,你将不得不等待轮到你,而作业队列是快速通票这可以让你在完成上一个之后再骑一次。
这是Promises(和Async / await,它建立在promises上)和普通的旧异步函数setTimeout()或其他平台API之间的巨大差异。
javascrit的事件循环是这门语言中非常重要且基础的概念。清楚的了解了事件循环的执行顺序和每一个阶段的特点,可以使我们对一段异步代码的执行顺序有一个清晰的认识,从而减少代码运行的不确定性。合理的使用各种延迟事件的方法,有助于代码更好的按照其优先级去执行。