面试官:讲讲什么是事件循环吧?
作为面试常考的知识点之一,我们都应该知道事件循环的重要性,网上有非常非常多的文章是讲解事件循环的,但是我总觉得讲的还是不够全面,很多小伙伴看完后可能大概了解了事件循环的执行机制,了解了同步任务和异步任务是什么,但又被宏任务、微任务的出现搞懵了,或者说是一看就懂,一做就错。看完这篇文章,包你弄懂事件循环。
如何完全弄明白事件循环?把下面六点都弄懂就行了!
1. 什么是事件循环?
2. 为什么会有事件循环?
3. 事件循环的机制是怎样的?
4. 同步任务、异步任务、宏任务、微任务这四个词之间有什么关系?
5. ES6里的async和await又是什么玩意?
6. 经典面试题(一起做题吧~)
1.什么是事件循环?
首先我们得知道,Javascript是单线程的语言,通俗来讲就是我们写的js代码,运行的时候都是按我们写的顺序从上到下一行一行执行的。事件循环其实就是Javascript基于单线程执行的一种执行机制。
为什么javascript是单线程的语言?
Javascript从出生的时候就被下了一个定义,是为了用户和浏览器交互而诞生的,让我们想想,如果我们写的js代码里面有两个操作,一个是向文档中增加一个DOM节点,一个是删除那个DOM节点,如果JS是多线程的话,浏览器如何知道我们到底是要增加还是删除DOM节点先呢?浏览器很可能就不是按我们的原意进行操作了。
2.为什么会有事件循环?
js是单线程执行的,如果说我们写的js有一个步骤(比如发请求获取某个资源)由于我们网络缓慢需要花费很久的时间,那我们岂不是要一直等资源获取到了才继续执行下面的代码?JS可是会阻塞页面渲染的,这样用户体验将会非常差,所以事件循环就诞生了。聪明的javascript引擎把我们要执行的js代码看成一个一个任务,任务可以分为两大类:
-
同步任务
同步任务都是那些不用太花费时间执行的js任务,可以说是普通的js代码。
-
异步任务
既然同步任务不太花费时间,那异步任务肯定就是需要花费较多时间的拉,举个例子把,setTimeout就是一个异步任务(这里先说一个,下面有完整版)。
当执行js遇到同步任务就一直往下执行,要是遇到异步任务了,我们就先把它挂起来,然后可以执行的时候就放进任务队列。继续执行下面的js任务,这样就不会阻塞拉!详细的循环机制再往下看。
3. 事件循环的机制是怎样的?(当还没有宏任务,微任务概念的时候)
放一张网上盛传的我觉得非常好的一张图来帮助理解事件循环
我们把script里的代码放在主线程执行,当遇到同步任务就执行,当遇到异步任务的时候就挂起(这里的注册回调函数的过程是怎么样的呢,比如setTimeout两秒,两秒会后把回调函数放进异步任务队列,等待主线程任务执行完然后再执行异步任务队列里的过程)。一点都不直观? 来个题把,下面这段代码会输出什么呢?
首先我们的代码会进入主线程执行
- 第1行,同步任务
console.log('1'),打印1 - 第2行,异步任务
setTimeout挂起,等300ms后将回调函数放入异步任务队列等待执行。(队列的特点是什么?先进先出) - 第8行,
console.log('4'),打印4 - 同理,第9行,第12行的任务都是
setTimeout,都先挂起等待够时间,然后把回调函数放入异步任务队列 - 第15行,
console.log('7'),打印7
执行完后主线程,异步任务队列的情况:
接着主线程为空了,把任务队列按照先进先出的顺序放入主线程执行。
console.log('5'),先打印5console.log('6'),然后打印6- 然后
console.log('2'),打印2,又遇到了setTimeout console.log('3')再挂起,然后放入任务队列 - 主线程还是空,然后把唯一刚刚放入异步任务队列的
console.log('3')执行
所以最后结果是什么?
结果:1,4,7,5,6,2,3
是不是也挺简单的其实?接下来让我们看看宏任务和微任务又是什么东西(真的很简单的)。
4. 同步任务、异步任务、宏任务、微任务这四个概念之间有什么关系?
浏览器的异步任务只有setTimeout一个这么简单吗?那是不可能的。
随着各种异步任务的出现,我们不得不对js的所有任务再次进行了分类,分为了宏任务,微任务。(宏任务和微任务可以完美地契合所有事件,解释事件循环的机制)
所有任务(包括之前所说的同步任务、异步任务)被分为了宏任务,微任务,而且现在异步任务的队列有两个(宏任务队列,微任务队列)。
首先让我们看看宏任务,微任务都有哪些。(找了个很全的图)
别看有这么多,其实面试常考的就只有setTimeout、setInterval、Promise这几个,但是其他最好也要了解了解。
那事件循环机制由宏任务和微任务来解释是怎样的呢?看图:
还是老样子,结合题目理解,还是刚才那段代码,不过加了两个promise(包含有微任务)
开始分析:
第一轮:
- 首页,这整段代码是什么?可以看作是
script包含的代码段呀,是宏任务。直接放入主线程执行。 - 第1行,
console.log('1'),打印1 - 第2行,
setTimeout异步宏任务,放入宏任务队列 - 第8行,
promise有异步微任务(then里面的),放入微任务队列 - 第13行,
console.log('4'),打印4 - 第14行,
setTimeout异步宏任务,放入宏任务队列 - 第22行,
setTimeout异步宏任务,放入宏任务队列 - 第25行,
console.log('7'),打印7
这时主线程、宏任务队列、微任务队列的情况:
根据微任务优先执行的原则
- 执行
console.log('8')( 微任务,微任务队列为空了,才执行宏任务,不然会一直执行完所有微任务 ) - 执行
console.log('5')( 遇到promise console.log('9')放入微任务队列,注意!!!每一个宏任务队列的任务执行完后都会看看微任务队列有没有任务执行,明显是刚放进去一个console.log('9'),执行console.log('9'),好了微任务队列为空了,继续执行宏任务 ) - 执行
console.log('6'),执行完了,问微任务队列有微任务吗?没有,所以继续执行宏任务队列的任务 - 执行
console.log('2'),遇到打印3的宏任务放入宏任务队列,又问微任务队列有微任务吗...没有,继续执行宏任务队列的下一个任务 - 执行
console.log('3'),又问微任务队列有微任务吗...没有,继续执行宏任务队列的下一个任务,发现没任务了,好了执行完了。
所以结果是:1,4,7,8,5,9,6,2,3
没看懂的小伙伴多看几遍,注意每次执行到微任务的时候都是把微任务队列清空了才执行宏任务的,而每次执行宏任务的时候只是从宏任务队列取一个宏任务执行,执行完又会问有没有微任务,有就执行(执行完所有微任务),没有就继续执行剩下的宏任务。
得出结论,事件循环 即是:执行队列中的所有微任务(然后看看有无宏任务) -> 执行队列中的所有宏任务(然后看看有无微任务) -> 执行队列中的所有微任务(然后看看有无宏任务)... 这就是事件循环
你悟了吗?
5. ES6里的async和await又是什么玩意?
我们可以把这两个东西看成promise.then的形式。
例如:
不多说了,你细品,这两个结果是相等的,可以打开控制台试试。
6. 经典面试题(一起做题吧~)
奉上一道 经典面试题(答案在题目下面):
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
}).then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('script end')
结果: script start -> async2 end -> Promise -> script end -> async1 end -> promise1 -> promise2 -> setTimeout
完结
好了,终于把事件循环讲完了!你学会了吗?
若小伙伴们发现有错误欢迎指正和探讨!
要是觉得讲的还可以的话可以点个赞哦