本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!
JS 引擎并不提供事件循环
在谈事件循环的时候,经常会说请说一说JS的事件循环机制。这常常会让人产生一种误解,事件循环好像是JS语言的特性。
实际上并不是,要弄清楚这个问题,我们先要明晰这些概念。
宿主环境、JS引擎、渲染引擎
宿主环境包含JS引擎和渲染引擎。最常见的宿主环境就是浏览器提供的环境。
- JS引擎:运行JavaScript代码。最出名的是Chrome的V8引擎。
- 渲染引擎:对网页进行排版和显示。最出名的是Webkit引擎。
注意,JS引擎运行在JS线程,渲染引擎运行在渲染线程。浏览器让他们互斥,当一个线程运行的时候,另一个线程就得挂起。
除了浏览器,Node.js也是一个主要的宿主环境,这个环境就不包含渲染引擎了。它也提供了同浏览器稍有不同的事件循环机制。
宿主环境提供事件循环
因为JS引擎并不提供事件循环,且关键的一点,JS是单线程语言,所以浏览器需要有一种机制,让JS能处理异步任务,于是有了时间循环机制。
我们来列举一些浏览器有那些异步任务:
- DOM事件。如鼠标点击产生的回调。
- http请求产生的回调。
- setInterval和setTimeout。定时器产生的回调。
这些产生的回调,就会进入事件队列。等JS引擎空闲了,依次的执行它们(但是定时器肯定得等它们到时间了,才执行它们)。
宏任务 和 微任务
上面说的那些,都是宏任务。是宿主让JS拥有了处理异步的能力。但宏任务还是有不足之处的,毕竟是宿主提供的API,涉及跟JS引擎交互,性能上肯定有点问题。同时,JS本身也意识到了自己的不足,区区一个单线程语言,本身没有处理异步的能力。
随着时间的推进,JS语言自身支持异步编程,也就是Promise来了(也就是微任务)!对于JS语言来说,弥补了自己异步能力的缺陷,也为前端开发人员,提供了多一种选择,来处理异步。
这也就是宏任务和微任务的本质:
- 由宿主提供的,为宏任务。
- 由JS语言自身提供的,为微任务。
那么,它们的执行时机,解释起来就非常的合理了。在JS代码执行的过程中,产生了一些宏任务和微任务。当前的任务执行结束后,把所有准备好的微任务都执行了,因为它们是JS引擎内部的,先统统执行完,再考虑跟外边交互。等微任务执行完了,好了,去宿主那看看,有没有需要交给我处理的宏任务。
如下图:微任务队列在JS引擎内,宏任务队列在JS引擎外。将准备好的微任务都执行完毕后,再去外边找一个宏任务来执行。
一次事件循环还做了什么
除了针对JS引擎的部分,事件循环还有针对渲染引擎的部分。
如下图所示,每次事件循环,会检查一下是否需要渲染,如果需要,就把JS引擎挂起,让渲染引擎开始工作,渲染网页,这样整个流程就完整了。
vue的nextTick是宏任务还是微任务
可以是宏任务也可以是微任务。但是默认优先使用微任务。只有在宿主环境不支持微任务的时候,才会回退到使用宏任务。
显然,微任务是更好的选择,拥有更高的执行效率。但也让我产生了疑问,this.$nextTick的回调可以拿到更新后的DOM。更新后的DOM?是渲染后的DOM吗?那岂不是得等一次事件循环结束,浏览器渲染完成页面,才能拿到最新的DOM。那不得用宏任务才行,微任务的执行,并没有完成一次事件循环,新的DOM元素也没有渲染啊。
这里我理解有误,正确的理解应该是这样的,更新DOM是指更新内存中的DOM,并不是指渲染到显示器上。所以,我们用this.$nextTick注册的回调,会同更新DOM的方法,一同顺序的在下一个微任务中执行。是可以拿到更新后的DOM的。
结语
总的来说,事件循环是JS在宿主环境中可以完成任务的重要机制。与JS息息相关,却不是JS语言内的东西。
以上理解是看了多篇文章,加之自己的一点理解总结,感觉逻辑上是合理的,如有问题,欢迎指出。