[JS]带你通俗了解微任务,宏任务

2,235 阅读5分钟

前言

最近想重新系统地整理一下前端的知识,因此写了这个专栏。我会尽量写一些业务相关的小技巧和前端知识中的重点内容,核心思想。

微任务,宏任务这个东西,在我刚毕业那段时间很少听人提起。不知道为什么近几年的面试好像突然很变得常见。今天我们就来了解一下,究竟什么是微任务,宏任务。

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这个异步任务之后,,会把回调任务中的内容放到异步任务队列中,等异步任务可以执行后再加入到主任务队列中。

归纳:

  1. 当遇到异步任务时,会先把异步任务放到等待队列中。
  2. 当等待队列中的任务,可以被执行了,就会加入主队列。因此等待队列任务的执行顺序跟加入顺序无关。

微任务,宏任务又是什么?

当我们了解过了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。

  1. 先同步输出aaa.
  2. 遇到宏任务t1,加入队列。
  3. 同步输出111,222。
  4. 遇到微任务333,加入队列。
  5. 遇到宏任务t2,加入队列
  6. 遇到微任务444,加入队列
  7. 同步输出bbb,同步结束。
  8. 回来先执行微任务,输出333,444。微任务结束
  9. 执行宏任务,输出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】分类的文章中。

参考

juejin.cn/post/684490…

juejin.cn/post/689407…

juejin.cn/post/684490…

www.ruanyifeng.com/blog/2013/1…