欢迎你们来到"吊打面试官"系列,Blue在这个系列中,会和你们分享各类面试中的知识和“坑”,欢迎你们关注我,精彩内容不错过,若是碰到感兴趣的题目想讨论,欢迎你们经过留言告诉我,谢谢,但请记住:javascript
面试或许能够应付,但不要糊弄本身,完全掌握知识自己才是提高的关键 ———— Blue说的前端
事件循环(Event Loop)、宏任务和微任务这几个概念,是最近面试中常常问到的知识点,理解它不但能帮助咱们更好地应对面试,也能完全理解js的执行机制,写出更为高效的代码,本文将帮你完全理解事件循环、宏任务、微任务,欢迎点赞、收藏、评论、转发java
内容大纲web
在JS中咱们常常会须要“同时”进行多项工做,例如:定时器、事件、异步数据交互等,那么JS是如何管理这些任务的,又是如何肯定他们的执行顺序的?面试
首先,全部的语言都拥有并发模型的概念,也就是说多个任务如何同时执行,大部分语言支持多线程执行,JS拥有全部语言中最简单的并发模型——JS使用单线程的"事件循环(Event Loop)"来处理多个任务的执行编程
咱们用示意性的代码,来表示js的事件循环数组
while(获取任务()){
执行任务();
}
复制代码
简单来讲,js的事件循环,每次读取一个任务,而后执行这个任务,执行完再继续获取下一个,若是暂时没有任务,就暂停执行,等待下一个任务到来;若是在执行任务的过程当中有新的任务到达,也不会中断现有任务的执行,而是添加到队列的尾部等待promise
结论是,JS使用基于事件循环的单线程执行方式,并且是非抢断执行的(也就是说,不管发生什么,都会把当前任务执行完,不会出现执行到一半就去执行别的任务的状况),你们可能会奇怪,这样作不是性能很低吗?确实,我们来看看这样作的优缺点浏览器
多线程(C、Java等语言) | 单线程事件驱动(JavaScript) | |
---|---|---|
复杂性 | 复杂度高,须要面对线程间同步等大量消耗头发的问题 | 简单易于使用,永远不会出现资源争抢的问题 |
性能 | CPU性能很高,适合计算密集型任务 | 单一线程,没法发挥CPU的极限性能(可经过webWorker补充),不过前端应用本就不是计算密集型的 |
阻塞 | 不会阻塞,大型任务能够单开线程处理 | 其实也不会阻塞,由于JS中的IO任务都是异步的(文件、网络),虽然大型计算任务依然会阻塞UI线程,但这种状况对前端其实很少 |
因此,JS的单线程事件循环其实很适合前端使用,大幅的简化了程序的复杂度,同时前端少会有大型计算任务,因此性能也并不是问题markdown
结论:单线程事件循环看着好像"有点low",但其实很是适合前端开发
理解了事件循环的概念,咱们来继续看看任务队列,所谓任务队列,其实就是保存待处理任务的一个数组
每当咱们要执行一个新的任务(例如:定时器),咱们就会在队列尾部添加一个task,等到当前任务完成,事件循环会去队列头部寻找下一个可执行任务,咱们用一个例子来更好的理解这一点
console.log('aaaa');
setTimeout(()=>{
console.log('cccc');
}, 0); //这个0毫秒是重点
console.log('bbbb');
复制代码
用咱们上面说的任务队列的思想,来分析这个程序执行的过程:
console.log('aaaa')
,很普通的同步代码console.log('bbb')
console.log('cccc')
说了这么多,看图更容易理解
总结一下:
你们必定注意过一个事情,那就是JS中的定时器常常不许(其实全部语言都这样),这个问题也跟上面的任务队列有关
因此结论就是,由于有其余任务在排队,定时器永远不可能彻底准时
上面的东西你们都没问题了的话,再给你们一个例子,检测一下本身的学习成果
console.log('aaa');
setTimeout(() => console.log(111), 0);
setTimeout(() => console.log(222), 0);
console.log('bbb');
复制代码
相信这个也难不倒你们了,简单来讲,setTimeout即便是0毫秒,也不会当即执行,而是堆在队列尾部等待,而当前任务不会被打断,因此aaa和bbb先出来,而后再从队列尾部拿出一个任务,也就是111,而后再拿一个222
上面咱们讲了关于事件循环和任务队列的问题,那么接下来blue要告诉你们一个惊人的事情(有啥好惊人的...)
其实js里任务队列不仅有一条,而是有两条,并且有一条仍是SVIP年费白金队列
在考虑微任务的状况下,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');
复制代码
Blue带你来看一下,这个东西的执行过程
aaa
,这个没任何疑问111
去排队了,可是注意,定时器是宏任务222
也去排队了,但Promise进的是VIP队列bbb
了,并且当前任务就结束了,接下来是重点222
获得优先执行,毕竟是VIP嘛111
的那个定时器才获得执行因此,整个执行过程是aaa,bbb,111,222,如今咱们也明白了微任务是什么,其实微任务就是获得优先执行的异步任务
按照官方的设想,任务之间是不平等的,有些任务对用户体验影响大,就应该优先执行,而有些任务属于背景任务(好比定时器),晚点执行没有什么问题,因此设计了这种优先级队列的方式
上面咱们说到Promise也是微任务,并且async就是promise的一种语法包装(所谓语法糖),那async是否是必定是按照微任务的方式执行呢?"不全是"
来吧,直接上个例子,在你们蒙圈以前捞一下
console.log('aaa');
(async ()=>{
console.log(111); //在async里面
})().then(()=>{
console.log(222); //在async的then里面
});
console.log('bbb');
复制代码
相信我不说你们也能看出来,这个程序的坑就在111
和222
这里,换句话说,async究竟是怎么个异步法?
先上结果,再说缘由
aaa
,过async
是异步操做,但async函数自己(也就是111所在的()=>{}),其实依然是同步执行的,除非有await出现,这个下面会说,因此,这里111
会直接同步执行,而不是放到队列里等待then
不会同步执行,它才是异步的,并且是一个微任务,因此222
不会当即执行,而是排到队列尾部bbb
没什么好说的,并且当前任务也就执行完成了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');
复制代码
咱们来看看这个东西怎么执行的:
aaa
不说了111
是同步执行的,上面说过222
这里很重要了,首先,console.log本身是同步的,因此当即就会执行,咱们能直接看到222
,可是await
自己就是then
,因此console.log(333)
没法直接执行,而是老老实实去排队,并且,由于整个async并未执行完,它的then(也就是444)没法触发ddd
应该也不用说,当前任务到这里执行完毕333
拉出来,而且执行了,这时整个async才算完成,因此把then推到队列中等待执行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');
复制代码
首先,这个例子的坑很是多,不过这个例子搞定了,这课你就算毕业了,加油
aaa
,过t1
会放入任务队列等待111
会直接执行,由于async自己不是异步的(上面有说)222
也会直接执行,可是接下来的console.log(333);
和setTimeout(()=>console.log('t2'), 0);
就塞到微任务队列里等待了bbb
毫无疑问,并且当前任务完成,优先执行微任务队列,也就是console.log(333)
开始的那里333
,而后定时器t2
会加入任务队列等待(此时的任务队列里有t1和t2两个了),而且async完成,因此console.log(444)
进入微任务队列等待444
,此时全部微任务都完成了t1
和t2
才会出来是时候梳理一遍Blue讲过的东西了,那么首先
感谢你们观看这篇教程,有任何问题或想和我交流,请直接留言,发现文章有任何不妥之处,也请指出,提早感谢