🔥「吊打面试官」完全理解事件循环、宏任务、微任务

欢迎你们来到"吊打面试官"系列,Blue在这个系列中,会和你们分享各类面试中的知识和“坑”,欢迎你们关注我,精彩内容不错过,若是碰到感兴趣的题目想讨论,欢迎你们经过留言告诉我,谢谢,但请记住:javascript

面试或许能够应付,但不要糊弄本身,完全掌握知识自己才是提高的关键 ———— Blue说的前端

事件循环(Event Loop)、宏任务和微任务这几个概念,是最近面试中常常问到的知识点,理解它不但能帮助咱们更好地应对面试,也能完全理解js的执行机制,写出更为高效的代码,本文将帮你完全理解事件循环、宏任务、微任务,欢迎点赞、收藏、评论、转发java

内容大纲web

  • 什么是事件循环、JS代码的内在执行机制
  • 什么是宏任务和微任务
  • async的坑
  • 总结、更新&补充

什么是事件循环?

在JS中咱们常常会须要“同时”进行多项工做,例如:定时器、事件、异步数据交互等,那么JS是如何管理这些任务的,又是如何肯定他们的执行顺序的?面试

首先,全部的语言都拥有并发模型的概念,也就是说多个任务如何同时执行,大部分语言支持多线程执行,JS拥有全部语言中最简单的并发模型——JS使用单线程的"事件循环(Event Loop)"来处理多个任务的执行编程

咱们用示意性的代码,来表示js的事件循环数组

while(获取任务()){
  执行任务();
}
复制代码

简单来讲,js的事件循环,每次读取一个任务,而后执行这个任务,执行完再继续获取下一个,若是暂时没有任务,就暂停执行,等待下一个任务到来;若是在执行任务的过程当中有新的任务到达,也不会中断现有任务的执行,而是添加到队列的尾部等待promise

1-事件循环

结论是,JS使用基于事件循环的单线程执行方式,并且是非抢断执行的(也就是说,不管发生什么,都会把当前任务执行完,不会出现执行到一半就去执行别的任务的状况),你们可能会奇怪,这样作不是性能很低吗?确实,我们来看看这样作的优缺点浏览器

多线程(C、Java等语言) 单线程事件驱动(JavaScript)
复杂性 复杂度高,须要面对线程间同步等大量消耗头发的问题 简单易于使用,永远不会出现资源争抢的问题
性能 CPU性能很高,适合计算密集型任务 单一线程,没法发挥CPU的极限性能(可经过webWorker补充),不过前端应用本就不是计算密集型的
阻塞 不会阻塞,大型任务能够单开线程处理 其实也不会阻塞,由于JS中的IO任务都是异步的(文件、网络),虽然大型计算任务依然会阻塞UI线程,但这种状况对前端其实很少

因此,JS的单线程事件循环其实很适合前端使用,大幅的简化了程序的复杂度,同时前端少会有大型计算任务,因此性能也并不是问题markdown

结论:单线程事件循环看着好像"有点low",但其实很是适合前端开发

任务队列的概念

理解了事件循环的概念,咱们来继续看看任务队列,所谓任务队列,其实就是保存待处理任务的一个数组

2-任务队列

每当咱们要执行一个新的任务(例如:定时器),咱们就会在队列尾部添加一个task,等到当前任务完成,事件循环会去队列头部寻找下一个可执行任务,咱们用一个例子来更好的理解这一点

实例:任务队列与定时器

console.log('aaaa');

setTimeout(()=>{
  console.log('cccc');
}, 0);  //这个0毫秒是重点

console.log('bbbb');
复制代码

3-执行结果

用咱们上面说的任务队列的思想,来分析这个程序执行的过程:

  • 第1步:它会先执行console.log('aaaa'),很普通的同步代码
  • 第2步:定时器是这个问题的关键,尤为是0毫秒的
    • 0毫秒意味着没有延迟,因此本应直接执行console.log('cccc'),可是...
    • 定时器不会马上执行,由于它不能打断当前任务(JS是非抢断执行),因此它只能被放到队列的尾部
  • 第3步:执行console.log('bbb')
  • 第4步:当前任务已经执行完成了,这时才会从任务队列中寻找下一个任务(也就是以前放入的定时器任务)
  • 第5步:执行定时器任务,也就是console.log('cccc')

说了这么多,看图更容易理解

3-0

3-1

3-2

3-3

3-4

总结一下:

  • 正在执行的任务(好比上面的console.log aaa和bbb)永远不会被打断,全部异步代码会被添加到队列等待执行
  • 定时器不论时间多短(哪怕是0)也不会马上执行,而是被放到任务队列尾部

题外话:定时器为何老是不许?

你们必定注意过一个事情,那就是JS中的定时器常常不许(其实全部语言都这样),这个问题也跟上面的任务队列有关

  • 定时器的时间,并非函数执行的时间,而是"最短x毫秒后,将任务添加到队列中"
  • 也就是说,除非队列彻底是空的,不然定时器的时间到了,它仅仅是开始排队罢了
  • 那么,队列有可能永远是空的吗?显然不可能,就算没有你的任务,浏览器也有不少它的工做——渲染、重排、清理内存什么的

因此结论就是,由于有其余任务在排队,定时器永远不可能彻底准时

实例:更多定时器、更多的任务

上面的东西你们都没问题了的话,再给你们一个例子,检测一下本身的学习成果

console.log('aaa');

setTimeout(() => console.log(111), 0);
setTimeout(() => console.log(222), 0);

console.log('bbb');
复制代码

4-执行结果

相信这个也难不倒你们了,简单来讲,setTimeout即便是0毫秒,也不会当即执行,而是堆在队列尾部等待,而当前任务不会被打断,因此aaa和bbb先出来,而后再从队列尾部拿出一个任务,也就是111,而后再拿一个222

4-1

4-2

4-3

4-4

4-5

4-6

任务生而平等,直到有人冲了VIP

上面咱们讲了关于事件循环和任务队列的问题,那么接下来blue要告诉你们一个惊人的事情(有啥好惊人的...)

宏任务?微任务?

其实js里任务队列不仅有一条,而是有两条,并且有一条仍是SVIP年费白金队列

  • 宏任务(macroTask或简称Task):普通的任务,正常执行
  • 微任务(microTask):SVIP年费白金会员任务,优先于宏任务执行(但依然是非抢断的)

5-带微任务的事件循环

在考虑微任务的状况下,JS的事件循环是按照这样的顺序执行:

  • 获取下一个任务,若是没有就进入等待
  • 当执行完一个任务以后,会执行全部排队的微任务
  • 而后再次获取下一个任务
while(获取任务()){
  执行任务();
  
  微任务队列.forEach(微任务=>{
    执行微任务();
  });
}
复制代码

因此,微任务其实比普通任务的优先级更高,由于在一个任务结束后,事件循环会找到并执行所有微任务,而后再继续查找其余任务,但这时候咱们会有两个问题:

  • 哪些任务属于宏任务,哪些任务属于微任务?
  • 宏任务有啥特殊的,为何它有这么大特权?

哪些操做属于宏任务?哪些属于微任务?

最先的js只有宏任务,而微任务是后来才加的

  • 宏任务:正常的异步任务都是宏任务,最多见的就是定时器(setInterval, setImmediate, setTimeout)、IO任务

  • 微任务:微任务出现比较晚,queueMicrotask、Promise和async属于微任务(固然,async就是promise)

说了这么多,来看个例子吧,瞬间帮你搞清楚

console.log('aaa');

setTimeout(() => console.log(111), 0); //异步任务
queueMicrotask(() => console.log(222)); //异步任务

console.log('bbb');
复制代码

6-执行结果

Blue带你来看一下,这个东西的执行过程

  • 第1步,毫无疑问的,确定是先执行aaa,这个没任何疑问
  • 第2步,定时器确定不是马上执行,因此111去排队了,可是注意,定时器是宏任务
  • 第3步,queueMicrotask也不会当即执行,因此222也去排队了,但Promise进的是VIP队列
  • 第4步,执行到bbb了,并且当前任务就结束了,接下来是重点
  • 第5步,再次查询任务以前(也就是那个while),会先完成全部微任务,因此此时,222获得优先执行,毕竟是VIP嘛
  • 第6步,完成全部微任务后(也就是第5步),会寻找下一个任务,这时111的那个定时器才获得执行

因此,整个执行过程是aaa,bbb,111,222,如今咱们也明白了微任务是什么,其实微任务就是获得优先执行的异步任务

为何要有微任务?

按照官方的设想,任务之间是不平等的,有些任务对用户体验影响大,就应该优先执行,而有些任务属于背景任务(好比定时器),晚点执行没有什么问题,因此设计了这种优先级队列的方式

注意:async的坑

上面咱们说到Promise也是微任务,并且async就是promise的一种语法包装(所谓语法糖),那async是否是必定是按照微任务的方式执行呢?"不全是"

来吧,直接上个例子,在你们蒙圈以前捞一下

console.log('aaa');

(async ()=>{
  console.log(111);  //在async里面
})().then(()=>{
  console.log(222);  //在async的then里面
});

console.log('bbb');
复制代码

相信我不说你们也能看出来,这个程序的坑就在111222这里,换句话说,async究竟是怎么个异步法?

先上结果,再说缘由

7-执行结果

  • 第1步,依然是毫无悬念的aaa,过
  • 第2步,虽然async是异步操做,但async函数自己(也就是111所在的()=>{}),其实依然是同步执行的,除非有await出现,这个下面会说,因此,这里111直接同步执行,而不是放到队列里等待
  • 第3步,重点来了,then不会同步执行,它才是异步的,并且是一个微任务,因此222不会当即执行,而是排到队列尾部
  • 第4步,执行bbb没什么好说的,并且当前任务也就执行完成了
  • 第5步,最后从任务队列中把排队的222拿出来,完成整个程序

是否是很好懂?那么,再来看看await的做用吧,await实际上是异步的,跟then差很少(从语法上来讲,await其实就是promise的then),直接上例子

console.log('aaa');

(async ()=>{
  console.log(111);
  await console.log(222);
  console.log(333);
})().then(()=>{
  console.log(444);
});

console.log('ddd');
复制代码

8-执行结果

咱们来看看这个东西怎么执行的:

  • 第1步、aaa不说了
  • 第2步、111是同步执行的,上面说过
  • 第3步、222这里很重要了,首先,console.log本身是同步的,因此当即就会执行,咱们能直接看到222,可是await自己就是then,因此console.log(333)没法直接执行,而是老老实实去排队,并且,由于整个async并未执行完,它的then(也就是444)没法触发
  • 第4步、ddd应该也不用说,当前任务到这里执行完毕
  • 第5步、从任务队列中把333拉出来,而且执行了,这时整个async才算完成,因此把then推到队列中等待执行
  • 第6步、把console.log(444)拉出来执行,看到444

因此,一个结论是,await其实等价于then(事实上他俩也确实是一个东西),都是将后续任务放到微任务队列中等待,而不会当即执行

再来巩固一下吧

都清楚了吧?那Blue再带你看个例子巩固一下吧

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');
复制代码

9-执行结果

首先,这个例子的坑很是多,不过这个例子搞定了,这课你就算毕业了,加油

  • 第1步、毫无悬念aaa,过
  • 第2步、t1会放入任务队列等待
  • 第3步、111会直接执行,由于async自己不是异步的(上面有说)
  • 第4步、222也会直接执行,可是接下来的console.log(333);setTimeout(()=>console.log('t2'), 0);就塞到微任务队列里等待了
  • 第5步、bbb毫无疑问,并且当前任务完成,优先执行微任务队列,也就是console.log(333)开始的那里
  • 第6步、执行333,而后定时器t2会加入任务队列等待(此时的任务队列里有t1和t2两个了),而且async完成,因此console.log(444)进入微任务队列等待
  • 第7步、优先执行微任务,也就是444,此时全部微任务都完成了
  • 第8步、执行剩下的普通任务队列,这时t1t2才会出来

总结

是时候梳理一遍Blue讲过的东西了,那么首先

15-三连

  • 事件循环:JS采用单线程的事件循环方式管理异步任务,优势是简化编程模型,缺点是没法发挥CPU的所有性能(但对前端其实没影响)
  • 任务队列:JS采用非抢断式运行,当前任务不会被打断,有新的异步任务时,会放入任务队列
  • 宏任务、微任务:宏任务就是普通异步任务,是最先出现的,微任务更关乎用户体验,因此获得优先执行
  • 常见宏任务:定时器、IO任务
  • 常见微任务:queueMicrotask、await、then

5-有bug?想补充?

感谢你们观看这篇教程,有任何问题或想和我交流,请直接留言,发现文章有任何不妥之处,也请指出,提早感谢

相关文章
相关标签/搜索