先上一道常见的笔试题javascript
console.log('1'); async function async1() { console.log('2'); await async2(); console.log('3'); } async function async2() { console.log('4'); } process.nextTick(function() { console.log('5'); }) setTimeout(function() { console.log('6'); process.nextTick(function() { console.log('7'); }) new Promise(function(resolve) { console.log('8'); resolve(); }).then(function() { console.log('9') }) }) async1(); new Promise(function(resolve) { console.log('10'); resolve(); }).then(function() { console.log('11'); }); console.log('12');
你们能够先配合下面这个图片思考一下输出顺序及这么运行的缘由java
上面简化图解可拆分为三部分:web
*Memory Heap 内存堆 —— 这是内存发生分配的地方面试
*Call Stack 调用栈 —— 这是代码运行时栈帧存放的位置segmentfault
咱们要知道的是,像setTimeOut DOM AJAX,都不是由JavaScript引擎提供,而是由浏览器提供,统称为Web APIspromise
javascript是一门单线程语言,虽然HTML5提出了Web-works这样的多线程解决方案,可是并无改变JaveScript是单线程的本质。浏览器
什么是H5 Web Works?就是将一些大计算量的代码交由web Worker运行而不冻结用户界面,可是子线程彻底受主线程控制,且不得操做DOM。因此,这个新标准并无改变JavaScript单线程的本质多线程
既然js是单线程的,就是同一时间只能作一件事情。那么问题来了,咱们访问一个页面,这个页面的初始化代码运行时间很长,好比有不少图片、视频、外部资源等等,难道咱们也要一直在那等着吗?答案固然是 不能异步
因此就出现了两类任务:async
那么咱们怎么知道何时主线程是空的呢?js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
setTimeout(fn,0)
这里的延迟0秒时什么意思呢?
含义是,当主线程执行栈内为空时,不用等待,就立刻执行。
setInterval和setTimeout相似,只是前者是循环的执行。对于执行顺序来讲,setInterval
会每隔指定的时间将注册的函数置入Event Queue,若是前面的任务耗时过久,那么一样须要等待。
对于setInterval(fn,ms)
来讲,咱们已经知道不是每过ms
秒会执行一次fn
,而是每过ms
秒,会有fn
进入Event Queue。一旦setInterval的回调函数fn执行时间超过了延迟时间ms,那么就彻底看不出来有时间间隔了。
事件循环的顺序,决定js代码的执行顺序。进入总体代码(宏任务)后,开始第一次循环。接着执行全部的微任务。而后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行全部的微任务。
除了广义的同步任务和异步任务,咱们对任务有更精细的定义:
setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.then()在本轮“事件循环”结束时执行。
不一样类型的任务会进入对应的Event Queue:
Promise中的异步体如今then
和catch
中,因此写在Promise中的代码是被当作同步任务当即执行的。
await其实是一个让出线程的标志。await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,而后就会跳出整个async函数来执行后面的代码;
由于async await 自己就是promise+generator的语法糖。因此await后面的代码是microtask。
下面开始分析开头的代码
console.log('1'); async function async1() { console.log('2'); await async2(); console.log('3'); } async function async2() { console.log('4'); } process.nextTick(function() { console.log('5'); }) setTimeout(function() { console.log('6'); process.nextTick(function() { console.log('7'); }) new Promise(function(resolve) { console.log('8'); resolve(); }).then(function() { console.log('9') }) }) async1(); new Promise(function(resolve) { console.log('10'); resolve(); }).then(function() { console.log('11'); }); console.log('12');
第一轮事件循环流程:
script
做为第一个宏任务进入主线程,遇到console.log
,输出1async一、async2
函数声明,声明暂时不用管process.nextTick()
,其回调函数被分发到微任务Event Queue中。咱们记为process1
setTimeout
,其回调函数被分发到宏任务Event Queue中。咱们暂且记为setTimeout1
async1
,遇到console.log
,输出2下面这里是最难理解的地方
咱们知道使用 async 定义的函数,当它被调用时,它返回的是一个Promise对象
而当await后面的表达式是一个Promise时,它的返回值其实是Promise的回调函数resolve的参数
await async2()
调用,发现async2也是一个 async 定义的函数,全部直接执行输出4,同时返回了一个Promise。划重点:此时返回的Promise被分配到微任务Event Queue中,咱们记为await1。await会让出线程,接下来就会跳出async1函数继续往下执行。 Promise
,new Promise
直接执行,输出10。then
被分发到微任务Event Queue中。咱们记为then1
console.log
,输出12宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout1 | process1 |
await1 | |
then1 |
上表是第一轮事件循环宏任务结束时各Event Queue的状况,此时已经输出了1 2 4 10 12
咱们发现了process1
、await1
和then1
三个微任务
process1
,输出5await1
,就是 async1 放进去的Promise,执行Promise时发现又遇到了他的真命天子resolve函数,划重点:这个resolve又会被放入微任务Event Queue中,咱们记为await2,而后再次跳出 async1函数 继续下一个任务。 then1
,输出11宏任务Event Queue | 微任务Event Queue |
---|---|
setTimeout1 | await2 |
到这里,已经输出了1 2 4 10 12 5 11
此时还有一个await2
微任务
它是async1 放进去的Promise的resolve回调,执行它(由于 async2 并无return东西,因此这个resolve的参数是undefined),此时 await 定义的这个 Promise 已经执行完而且返回告终果,因此能够继续往下执行 async1函数 后面的任务了,那就是console.log(3)
,输出3
到这里,第一轮事件循环结束,此时,输出顺序是 1 2 4 10 12 5 11 3
第二轮时间循环从setTimeout1
宏任务开始
console.log
,输出6process.nextTick()
,一样将其分发到微任务Event Queue中,记为process2
new Promise
当即执行输出8,then
也分发到微任务Event Queue中,记为then2
宏任务Event Queue | 微任务Event Queue |
---|---|
process2 | |
then2 |
上表是第二轮事件循环宏任务结束时各Event Queue的状况,此时输出状况是
咱们发现了process2
和then2
两个微任务
process2
,输出7then2
,输出9第二轮事件循环结束,第二轮输出6 8 7 9
整段代码,共进行了两次事件循环,完整的输出 1 2 4 10 12 5 11 3 6 8 7 9
到这里,你们应该已经清楚了JS的事件循环机制,后面无论在工做仍是面试中,确定都是游刃有余啦~
本篇是我开始的第一篇文章,还但愿你们多多支持,不吝赐教哇,也但愿能够提出意见或建议。
资料参考: