浏览器的事件循环,前端再熟悉不过了,天天都会接触的东西。但我之前一直都是死记硬背:事件任务队列分为macrotask和microtask,浏览器先从macrotask取出一个任务执行,再执行microtask内的全部任务,接着又去macrotask取出一个任务执行...,这样一直循环下去。可是对于下面的代码,我一直懵逼,setTimeout属于macrotask,按照上面的规则,setTimeout应该先被取出来执行啊,可是我却被执行结果打脸了。javascript
<script> setTimeout(() => { console.log(1) }, 0) new Promise((resolve) => { console.log(2) resolve() }).then(() => { console.log(3) }) // 我曾经的预期是:2 1 3 // 实际输出:2 3 1 </script>
通过再仔细看别人对任务队列的介绍,才知道,同步执行的js代码其实就算一个macrotask(准确说是每个script标签内的代码都是一个macrotask),因此上面的规则中说的 先取出一个macrotask执行 是没有问题的。
网上不少文章都是像上面这样解释的,我也一直认为这是HTML对事件循环的规范,咱们记着就是。直到最近看了李银城大佬的文章(见文末的参考连接),我才恍然大悟,以前看的文章都没有明确地从浏览器的多线程模型这个角度分析,因此让咱们以为浏览器的事件循环是基于上述的约定,但其实这是浏览器的多线程模型致使的结果。html
macrotask本质上是浏览器多个线程之间通讯的一个消息队列
在chrome里,每一个页面都对应一个进程,该进程又有多个线程,好比js线程、渲染线程、io线程、网络线程、定时器线程等,这些线程之间的通讯,是经过向对方的任务队列中添加一个任务(PostTask)来实现的。前端
浏览器的各类线程都是常驻线程,它们运行在一个for死循环里面,每一个线程都有属于本身的若干任务队列,线程本身或者其它线程均可能经过PostTask向这些任务队列添加任务,这些线程会不断地从本身的任务队列中取出任务执行,或者是处于睡眠状态直到设定的时间或者是有人PostTask的时候把它们唤醒。
能够简单地理解为,浏览器的各个线程都在不停地从本身的任务队列中取出任务,执行,再取出任务,再执行,这样无限循环下去。java
如下面的代码为例:chrome
<script> console.log(1) setTimeout(() => { console.log(2) }, 1000) console.log(3) </script>
console.log(1)
,而后执行setTimeout
,向定时器线程添加一个任务,接着执行console.log(3)
,这时js线程的任务队列为空,js线程进入休眠console.log(2)
。能够看到,所谓的macrotask并非浏览器定义了哪些任务是macrotask,浏览器各个线程只是忠实地循环本身的任务队列,不停地执行其中的任务而已。promise
比起macrotask是浏览器的多线程模型形成的“假象”,microtask是确实存在的一个队列,microtask是属于当前线程的,而不是其余线程PostTask过来的任务,只是延迟执行了而已(准确地说是放到了当前执行的同步代码以后执行),好比Promise.then、MutationObserver都属于这种状况。浏览器
如下面的代码为例:网络
<script> new Promise((resolve) => { resolve() console.log(1) setTimeout(() => { console.log(2) },0) }).then(() => { console.log(3) }) // 输出:1 3 2 </script>
new Promise
以及Promise中的resolve
,resolve后,promise的then的回调函数会做为须要延迟执行的任务,放到当前执行的全部同步代码以后setTimeout
,向定时器线程添加一个任务console.log(3)
console.log(2)
。经过上面的分析,能够看到,文章开头提到的规则:浏览器先从macrotask取出一个任务执行,再执行microtask内的全部任务,接着又去macrotask取出一个任务执行...,并无说错,但这只是浏览器执行机制形成的现象,而不是说浏览器按照这样的规则去执行的代码。多线程
最后,看了这篇文章,你们可以基于浏览器的运行机制,分析出下面代码的执行结果了吗(ps:不要用死记硬背的规则去分析哟)函数
console.log('start') const interval = setInterval(() => { console.log('setInterval') }, 0) setTimeout(() => { console.log('setTimeout 1') Promise.resolve() .then(() => { console.log('promise 3') }) .then(() => { console.log('promise 4') }) .then(() => { setTimeout(() => { console.log('setTimeout 2') Promise.resolve() .then(() => { console.log('promise 5') }) .then(() => { console.log('promise 6') }) .then(() => { clearInterval(interval) }) }, 0) }) }, 0) Promise.resolve() .then(() => { console.log('promise 1') }) .then(() => { console.log('promise 2') }) // 执行结果 /* start promise 1 promise 2 setInterval setTimeout 1 promise 3 promise 4 setInterval setTimeout 2 promise 5 promise 6 */