事件队列与同步异步事件
首先,Javascript 是一个单线程语言。所以就是说在一行代码的执行过程,必然不会存在同时执行另一行代码,也就是说一个时间只会干一件事。就像使用 alert()
(alert是同步执行的) 以后进行 console.log
,再 alert()
没有确认之前,控制台是不会有输出的。
所以,如果全部代码都同步执行,就会出现这样一个场景,如果我要从后端调接口获取一些数据,但由于代码是同步执行的,所以在后端没有返回数据之前,代码是不会往下执行的,程序也不会再处理其他任何操作,就在这里等接口数据返回,这个时候页面就是卡死的,这肯定是不行的。
于是就有了异步事件的概念,当我们请求发出去以后,向主程序注册一个回调函数,告诉主程序等接收到数据返回之后通知我,然后我们就可以去干其他事了。
这个时候可能就会有疑问了,不是说 javascript 是一个单线程语言吗,请求没有回来之前他应该等待请求结果啊,为什么请求发出去了,还没有回来,它就不管了,去做其他事了。这是因为发请求这件事,并不是 javascript 去做的,而是主程序去做的,在浏览器环境中,主程序就是浏览器,如果你仔细看过 javascript 发请求的文档,无论是 fetch
还是 xmlhttprequest
,都是 Web API ,也就是说这些方法都是浏览器提供的。那么我们就可以想到,发请求这回事应该是这么做的,javascript 向外发了一条消息,这个消息的类型是 fetch
或者 xmlhttprequest
, 消息内容是这个请求需要的数据,和请求结果回来之后通知给 javascript 的联系方式,然后浏览器收到了这个消息,然后就去发请求了,请求结果回来了,然后再根据 javascript 预留的联系方式,将结果通知给 javascript。
然后我们继续往下,当请求完成,通知到 javascript 时,javascript 可能正在做其他事,这个时候就需要在一旁等待,也就是说,异步事件即便完成了,也需要在一旁等待,不会被立刻处理 。我们的程序中肯定不会只有一个异步事件,所以异步事件多了,我们就需要对异步事件进行排序,缓存起来,等到同步的任务执行完了,再来执行这些异步事件,这些被缓存起来的异步事件,也就是事件队列。
事件循环与调用栈
当同步事件执行完了,就该执行被缓存起来的异步事件了,这些异步事件是怎么来的呢,就是我们发请求的时候,注册给主程序告诉主程序,请求回来了,把结果通知给我(异步事件的来源除了网络请求,还有 setTimeout
setInterval
等)。
然后我们的这些异步事件刚才不是被保存到事件队列里缓存起来了吗,这个时候,同步事件执行完了,就该把这个队列拉出来执行了,被拉到哪里呢,就是 javascript 的主线程,我们将其称为 调用栈,当这个队列被推到调用栈之后,这个事件队列就空了。
我们继续以请求为例往下说,浏览器把请求结果给了我们,我们需要做什么呢,这个时候,我们可能拿着这些数据,去计算某些结果,然后将计算的结果渲染到页面上,这些事情处理完,这一个异步事件就处理完了,然后处理下一个异步事件。
但是,有没有可能,当我拿到数据在页面渲染的时候,然后发现这些数据不全,缺少一些数据,我需要根据已有数据,去向后端获取新的数据,拿新的数据去填补之前的空缺,那这个时候就需要再次发送请求,相当于 javascript 再处理这个异步事件的时候,又出现了一个新的异步事件,我们管这个新的异步事件叫做 **a1
**,a1的父级异步事件我们叫做 **a
**。
那这个 a1
怎么处理呢,还是和所有的异步事件一样,先去发请求,然后向主程序注册回调函数,然后继续往下执行它的父级 a
的异步事件,将它父级 a
的异步事件中的事情做完(其他的同步事件),然后执行下一个异步事件。
那 a1
的请求回来了,浏览器通知到了我们,我们调用栈里的异步事件还没有执行完,这个时候需要怎么做呢,javascript 就会继续把 a1
的异步事件添加到之前空了的事件队列里,当调用栈里的事件处理完了,再将事件队列里的事件,推到调用栈,以此循环。这就是事件循环