前言
最近想重新系统地整理一下前端的知识,因此写了这个专栏。我会尽量写一些业务相关的小技巧和前端知识中的重点内容,核心思想。
微任务,宏任务这个东西,在我刚毕业那段时间很少听人提起。不知道为什么近几年的面试好像突然很变得常见。今天我们就来了解一下,究竟什么是微任务,宏任务。
EVENT LOOP
要聊微任务,宏任务,就没法避免要先来谈一下event loop。我们都知道JavaScript代码在浏览器或者nodejs中,都是单线程运行的,为了避免任务阻塞(如setTimeout的等待时间,其实可以干别的),js采用了一种叫event loop(事件循环)的机制。
简单来说就是当这个进程开始了之后,程序中会有一个任务队列,js程序按顺序地一个个任务执行。但为了避免阻塞,出现了我们熟知的【异步任务】。也就是遇到异步任务之后,程序不会立即执行。而是把他放到异步任务的队伍中,这样就会等【同步任务】执行完之后再去看异步任务中有没有可以执行的任务,如果有就把他拿到主程序中执行。
举个例子:
console.log(1);
setTimeout(()=>{
console.log(2);
},100);
setTimeout(()=>{
console.log(3);
},0);
console.log(4)
// 输出
// 1
// 4
// 3
// 2
像上述的例子中js程序逐行读取执行,但是执行到setTimeout这个异步任务之后,,会把回调任务中的内容放到异步任务队列中,等异步任务可以执行后再加入到主任务队列中。
归纳:
- 当遇到异步任务时,会先把异步任务放到等待队列中。
- 当等待队列中的任务,可以被执行了,就会加入主队列。因此等待队列任务的执行顺序跟加入顺序无关。
微任务,宏任务又是什么?
当我们了解过了event loop之后,就会明白event loop中有同步和异步任务两种。他们的执行逻辑就是遇到异步先放队列。这似乎就已经可以阐述整个event loop了。
但是实际上,在异步任务的执行中类型中又分了2种任务,就是【微任务】和【宏任务】。而他们的执行顺序有着一个重要的规律——先执行微任务,再执行宏任务。只要微任务队列中有可执行的任务,就得等可执行的微任务执行完,再执行下一个宏任务。
宏任务类型
任务 | 浏览器 | Node |
---|---|---|
I/O | ✅ | ✅ |
setTimeout | ✅ | ✅ |
setInterval | ✅ | ✅ |
setImmediate | ❌ | ✅ |
requestAnimationFrame | ✅ | ❌ |
微任务类型
任务 | 浏览器 | Node |
---|---|---|
process.nextTick | ❌ | ✅ |
MutationObserver | ✅ | ❌ |
Promise.then catch finally | ✅ | ✅ |
根据上述的规律我们再来重新修改一下我们的模型
当微任务队列中有内容时,要先执行完,再执行宏任务。注意:假如微任务执行完了,在执行宏任务的过程中,又产生了新的微任务。此时仍然需要按照这个规律,先执行完微任务,才能执行一下个宏任务!
为什么要区分微任务和宏任务?
js机制在对待任务时,认为他们应该是不平等的。也就是说执行更快的任务应该可以插队,不必等执行耗时就的先执行完。从更底层来说:
- 微任务是线程之间的切换,速度快。不用进行上下文切换,可以快速的一次性做完所有的微任务。
- 宏任务是进程之间的切换,速度慢,且每次执行需要切换上下文。因此一个Eventloop中只执行一个宏任务。
- 微任务执行快,一次性可以执行很多个,在当前宏任务执行后立刻清空微任务可以达到伪同步的效果,这对视图渲染效果起到至关重要的作用。
而往往视图的渲染是在宏任务执行之后的,先执行微任务可以确保在视图渲染之前,数据已经更新。
练习
console.log('aaa');
setTimeout(()=>console.log('t1'), 0);
(async ()=>{
console.log(111);
await console.log(222);
console.log(333);
setTimeout(()=>console.log('t2'), 0);
})().then(()=>{
console.log(444);
});
console.log('bbb');
// 输出
//aaa
// 111
// 222
// bbb
// 333
// 444
// t1
// t2
上面的例子中,有宏任务t1,t2。微任务333,444。
- 先同步输出aaa.
- 遇到宏任务t1,加入队列。
- 同步输出111,222。
- 遇到微任务333,加入队列。
- 遇到宏任务t2,加入队列
- 遇到微任务444,加入队列
- 同步输出bbb,同步结束。
- 回来先执行微任务,输出333,444。微任务结束
- 执行宏任务,输出t1,t2.
async/await
这里对于async/await,可能会有朋友有疑惑。其实这里是promise的知识范畴。因为本质他们是Promise的语法糖。因此我们需要了解一个小细节——promise括号内的内容是同步的。then后的才是微任务。
p = new Promise((res,rej)=>{
console.log(1);
res('ok');
console.log(2);
}).then((res)=>{
console.log(res);
})
console.log(3)
// 输出
// 1
// 2
// 3
// 4
同理我们再看看async/await
(async()=>{
console.log(1);
await console.log(2);
console.log('ok')
})()
console.log(3)
// 输出
// 1
// 2
// 3
// 4
这样应该可以帮助大家掌握了。
总结
微任务,宏任务实际上是event loop导致的结果。本身并不难,只要记住先微后宏的规律就行。但是event loop确是一个复杂的js机制。有兴趣的朋友可以自己查阅了解。本文的主旨在于,让大家可以通俗地快速了解这些概念。了解这些对于面试来说已经足够了,日常工作中对我们的影响其实很少。 值得注意的是event loop 在浏览器和nodejs中是有区别的。本文默认是以浏览器为宿主,关于nodejs的event loop之后会放在【nodejs】分类的文章中。