有一道面试题,关于宏任务和微任务的基础面试题,代码如下
console.log('1'); // 1
setTimeout(function() { // 2
console.log('2'); // 3
new Promise(function(resolve) { // 4
console.log('3'); // 5
resolve(); // 6
}).then(function() { // 7
console.log('4') // 8
})
})
new Promise(function(resolve) { // 9
console.log('5'); // 10
resolve(); // 11
}).then(function() { // 12
console.log('6') // 13
})
console.log('10') // 14
答案是:1,5,10,6,2,3,4
因为看到很多文章讲宏任务和微任务都是以文字和图片的形式讲解,这一次我试图通过动画的方式完整演绎整个运行过程
运行过程如下:
- 代码从上到下开始运行,这是一段代码块,会作为一个宏任务进入到任务队列中,然后事件循环在轮询的时候,会执行这段代码,暂且叫做全局任务(因为在全局作用域执行的)。首先执行第1行(代码中标识的“//1”)打印1。
- 执行到第2行的时候,发现这是一个setTimeout方法,js引擎会把setTimeout回调函数放进延迟队列中。是的,浏览器不是只有一个队列,还有其他队列,其中一个就是延迟队列,专名放像setTimeout, setInterval这些延迟任务的,其他还放浏览器内部的延迟任务。
- 接下来执行到第9行,promise,这是一个微任务。首先会马上执行传入promise函数里面的代码(有兴趣的同学可以了解一下Promise规范,然后自己实现一个Promise,参考这篇文章)。执行第10行,打印5。
- 当执行第11行遇到resolve()的时候,会把传进then方法里面的回调函数放进微任务队列中。
这里暂时插入一个知识,其实每一个宏任务中都会带有微任务队列,来放微任务的,常见的微任务就是Promise,MutationObserver。当前宏任务中的代码执行完毕后,继续执行微任务队列中的任务。 - 然后执行最后一行,第14行,打印10。
- 上面我们说了,当前宏任务执行完毕后,会去看微任务队列中有没有微任务。而之前有一个微任务,也就是第13行的代码。所以这个时候会打印6。
- 执行到这里,全局任务这个宏任务就执行完毕了,事件循环就取下一个宏任务,这个时候会去延迟队列中取任务。事件循环会在任务队列,延迟队列来回检查队列中的任务。如果发现setTimeout的定时结束,就会马上执行延迟队列中的到期宏任务,否则继续去任务队列中检查是否有新任务。发现延迟队列有一个任务,这个时候就执行第3行,打印2。
- 然后又遇到promise,一样的,先执行传入的函数,执行第5行,打印3。
- 这时遇到了resolve,照样会把then里面的回调函数放进微任务队列中。别忘了前面讲的,每一个宏任务中都有一个微任务队列,setTimeout属于宏任务。宏任务执行完毕后就去执行微任务,执行第8行,打印4。微任务执行完毕后,当前宏任务就退出事件循环。如果执行过程中,又产生了微任务,继续放进微任务队列中,直到微任务清空,才会执行下一个宏任务。