JavaScript微任务与宏任务

210 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、任务队列和事件循环

JavaScript单线程模型与事件循环

二、宏任务

宏任务(macro task),可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。

浏览器为了能够使得JS内部(macro)task与DOM任务能够有序的执行,会在一个(macro)task执行结束后,在下一个(macro)task 执行开始前,对页面进行重新渲染。

常见宏任务:

script(整体代码)

setTimout、setInterval

Ajax请求

I/0

postMessage

setImmediate(Node环境)

三、微任务

微任务(micro task),可以理解是在当前 task 执行结束后立即执行的任务。也就是说,在当前task任务后,下一个task之前,在渲染之前。

所以它的响应速度相比宏任务会更快,因为无需等待渲染。也就是说,在某一个宏任务执行完后,就会将在它执行期间产生的所有微任务k都执行完毕(在渲染前)。

常见微任务:

Promise.then

Object.observe

MutationObserver

Process.nextTick(Node环境)

为什么需要微任务呢?

其实使用宏任务,完全可以满足异步非阻塞的需求。假设我们在Promise中进行了一系列异步操作以后,然后resolve,如果Promise.then也是宏任务的话,那么就要排队,即需要排在任务队列的最后,如果前面已经有许多任务在排队了,可能就需要等很久,而一般来说,我们认为Promise.then中的回调应当是比较及时的,所以我们将它赋于插队的特权,也就是微任务。

四、看代码说结果

 console.log(1);
 ​
 let p = new Promise((resolve, resject) => {
     console.log(2)
     setTimeout(function() {
         resolve(3)
     }, 10);
 })
 ​
 p.then(res => console.log(res));
 ​
 setTimeout(function() {
     console.log(4)
 }, 10);
 ​
 console.log(5);

输出是1 2 5 3 4。

解释:

首先运行代码,输出1,接着new Promise中的代码也是同步运行的,所以输出2,遇到setTimeout,交给webapi(10ms后放入宏任务队列),遇到第二个setTimout,与前一个一样,回调放入宏任务队列,接着输出5

此时主线程代码已经跑完了,现在任务队列中只有宏任务队列中有两个setTimeout的回调。去除第一个setTimeout的回调,resolve(3)产生微任务p.then,此时主线程代码再次完成,但是产生了微任务Promise.then,所以先执行微任务输出3,微任务队列清空,此时再去宏任务队列运行第二个setTimeout的回调,输出4,宏任务和微任务队列均为空,完成。