为什么要使用事件循环
JavaScript基于单线程执行,所以任务都是一个一个执行的,开始执行任务直到处理完成之前不会被打断,如果某个任务执行的时间很长,会阻塞页面。
那不想出现阻塞页面的情况怎么办呢?使用异步呗,让这个事件先到一边去完成,等完成了再回来汇报结果,这样就不会阻塞页面了。
但是要怎么回来?什么时候回来?这个就需要事件循环来解决。
事件循环基于两个基本原则
- 一次处理一个任务
- 一个任务开始后直到运行完成,不会被其他任务中断
事件循环的机制
任务是一个一个执行的,而且一次只能解决一个,这能怎么办?排队呗!开辟一个队列,第一个到的先接待,其他的就在后面等着呗
事件循环通常至少需要两个任务队列
上面这个图很重要!记记记!
注意:单次循环迭代中,只处理一个宏任务,其他宏任务等待下次循环迭代,而队列中的所有微任务都会一次性处理完毕,处理微任务时不能产生新的微任务
微任务相比宏任务来说优先级高,且大部分时候耗时都很小,很快就能处理掉,所以宏任务就只能老老实实排队,微任务就像vip客户,只要你来了,不管来了多少,前面的普通客户处理完毕之后,马上就开始接待vip的客户,等接待完了这些vip客户才继续接待普通客户
小细节
- 宏任务队列和微任务队列的检测都是独立于事件循环的,这就是说将任务添加到这两个队列中的行为是发生在事件循环外的
- 所有的微任务会在下一次渲染UI之前执行完成,因为他们的目标是在渲染前更新应用程序的状态
用例子看懂事件循环
使用计时器
计时器能延迟一段代码的运行,延长时间至少是指定的时长
方法 | 描述 |
---|
setTimeout | 启动一个计时器 | clearTimeout | 关闭一个计时器 | setInterval | 启动一个间隔计时器 | clearInterval | 关闭一个间隔计时器 |
观察下面的一段代码,模拟一连串事件的执行过程
setTimeout(() => {
}, 10)
setInterval(() => {
}, 10)
依据以上的情景,得到以下分析步骤
分析:
- 代码设置了两个计时器,一个是单次延时的计时器,一个是循环执行的间隔计时器,两个计时器都是延迟10ms执行
- 主线程执行耗时18ms
- 单次延时的计时器的回调函数执行需要耗费6ms
- 间隔延时的计时器的回调函数执行需要耗费8ms
流程:
-
0ms开始时,在事件队列中只存在主线程,立即执行 -
执行到10ms时,启动了两个计时器,有两项任务加入事件队列(图中红色部分) -
执行到18ms时,主线程执行完毕,此时没有微任务,开始下一轮事件循环,事件队列不为空,继续执行下一项事件 -
执行到20ms时,间隔计时器再次启动,但是此时事件队列中已经存在了尚未执行的耗时8ms事件(红色),所以此次事件不会加入到事件队列中 -
执行到24ms时,耗时6ms事件(红色)也处理完毕,此时也没有微任务,继续处理事件队列中的下一项事件 -
执行到30ms时,间隔计时器再次启动,此时8ms事件(红色)已经被执行,可以加入新的事件(橙色)进入事件队列 -
执行到32ms时,8ms事件(红色)处理完毕,没有微任务,继续执行橙色事件 -
执行到40ms时,橙色事件处理完毕,没有微任务,正好间隔计时器此时也启动了,绿色事件加入到了事件队列中,事件队列不为空,则继续执行绿色任务 -
执行到48ms时,绿色事件处理完毕,没有微任务,事件队列也是空的,则等待事件加入队列,此时空闲 -
执行到50ms时,间隔计时器启动,蓝色事件加入队列,队列此时不为空了,直接执行蓝色事件 -
接下来就是在9.和10.之间反复循环
值得注意的是,计时器只能控制任务何时被加入事件队列中,而无法控制何时执行
宏任务与微任务交杂(同步与异步)
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
setTimeout(() => {
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => console.log(5))
}, 0)
new Promise(resolve => {
console.log(6)
resolve()
}).then(() => console.log(7))
流程:
- 主线程是第一个宏任务,直接输出是1、3、6,主线程任务完成,检查微任务队列,有一个输出7的promise回调函数,则输出7,此时微任务队列为空,继续事件循环
- 接着执行第二个宏任务,输出2,完成该宏任务,检查微任务队列,没有微任务,继续事件循环
- 执行第三个宏任务,是一个立即执行的promise,输出4,回调函数加入微任务队列,该宏任务完成,检查微任务队列,发现有微任务,执行微任务输出5
件循环 - 接着执行第二个宏任务,输出2,完成该宏任务,检查微任务队列,没有微任务,继续事件循环
- 执行第三个宏任务,是一个立即执行的promise,输出4,回调函数加入微任务队列,该宏任务完成,检查微任务队列,发现有微任务,执行微任务输出5
- 事件循环的宏任务、微任务队列均为空,等待任务加入队列
|