本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、任务队列和事件循环
二、宏任务
宏任务(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,宏任务和微任务队列均为空,完成。