![ee6163780ed554d1865b7cc67be2ae30.png](https://i-blog.csdnimg.cn/blog_migrate/a15fea4e33592af0b40f18e0af617a2a.jpeg)
什么是事件循环机制
众所周知,js是单线程的。这意味着js一次只执行一个任务,且在当前任务执行完成之前,不会执行下一个任务。对于比较耗时的操作,比如从服务端获取数据,如果在请求过程中js一直等待服务器响应之后才执行后续任务,那么在此过程中就会造成阻塞。试想如果后续的任务并不依赖服务器返回的结果,有没有一种机制可以让js “跳过” 等待,继续执行后续任务呢?为了解决了这个问题,js把所有任务分为 同步任务 和 异步任务。同步任务在主线程上运行,异步任务则都被放入 任务队列,不会阻塞主线程。主线程会不断从任务队列中读取任务,整个过程是循环不断的,所以这种机制被称为 事件循环。
宏任务 & 微任务
如上所述,所有的异步任务都被放入任务队列,然而任务队列并不对所有任务一视同仁,而是把任务分为两种:宏任务(macrotasks) 和 微任务(microtasks)。宏任务被放入宏任务队列(macrotask queue),微任务被放入微任务队列(microtask queue)。
常见的宏任务有解析HTML,执行主线程代码,定时器setTimeout, setInterval等;常见微任务有DOM变动,Promise。微任务是更新应用状态的微型任务,需要在浏览器进行其他操作(例如重渲染UI)之前执行。
事件循环的流程
事件循环应该至少包含一个宏任务队列和一个微任务队列。实际上,为了区别不同任务的优先级,事件循环可能会包含多个队列,用于存放不同类型的宏任务和微任务。事件循环建立在两个基本原则之上:
- 一次只执行一个任务
- 一个任务在执行完成前不能被其他任务打断
请先看下图(图片来源于 Secrets of the Javascript):
![c088b81914b5fe75bdb06c25f893ca76.png](https://i-blog.csdnimg.cn/blog_migrate/b165dae1c23a6c8147285ca4314bc3d1.jpeg)
js执行完主线程之后,会检查宏任务队列中是否有等待执行的宏任务。若有,会从宏任务里提取一个最先入队的任务,此任务执行完成之后,进行下一步;若没有,则直接进行下一步。
接下来,事件循环会处理微任务队列。若微任务队列里有等待执行的微任务,事件循环会提取任务并执行完成,完成之后,会继续检查微任务队列中是否有等待执行的微任务。此过程会一直循环,直至微任务队列被清空。
至此,我们已看出事件循环处理宏任务队列和微任务队列的不同之处:在单次循环中,最多只会处理一个宏任务,但是会处理所有的微任务。
当微任务队列被清空之后,事件循环会检查是否有必要更新渲染UI,若需要,则会重渲染UI。至此,此次循环完成,然后事件循环会继续检查宏任务队列,开启下一次循环。
图中有几个细节需要注意:
- 任务队列处于事件循环之外。 这表示添加任务到对应队列的动作发生在事件循环之外。试想一下,如果不是这样的话,所有发生在js执行过程中的事件都会被忽律。为了避免丢失事件,检测和添加任务的动作都被独立于事件循环之外。
- 两种任务都是一次只执行一个任务
- 所有的微任务都需要在下次渲染之前完成,因为它们的目标就是在下次渲染之前更新应用状态。
- 为了达到60帧率(60fps),浏览器每秒会有60次尝试渲染面。这意味着若想让页面更加丝滑,在单次事件循环里我们没有太多时间用于执行任务。在理想状态下,一个任务和它生成的所有微任务,都应在16ms内完成
请思考以下几个问题:
1. setTimeout(fn, 10), fn一定会在10毫秒时被调用吗?
1. setInterval(fn, 10), fn一定会每隔10秒执行一次吗?若执行100毫秒,fn一定会调用10次吗?
我会在后续的文章中对以上问题进行解答,并详情阐述原因。如果你觉得这片文章还不错的话,敬请期待。若对文中内容有异议,欢迎指正讨论。
参考文献
- Secrets of the Javascript
- 阮一峰 - JavaScript 运行机制详解:再谈Event Loop