80% 的前端开发都答不上来的 js 异步面试题
最近面试中碰到了一道关于 JS 执行顺序的题目,题目比较基础,但是如果对于 JS 不熟的话,还是容易答不上来。再次记录和分析此次面试题,希望对大家有所帮助。
话不多说,先上结果
宏任务 微任务
如果看官是个新手的话,看到上面的输出结果,肯定一脸懵逼的,但是没关系,看完这篇文章您就懂了。想完全明白上面这道题目,还需要了解 JS 的两个概念,没错,就是宏任务和微任务。
首先看官肯定知道 JS 是单线程,实现异步的方法就是定时器和 es6+出现的 promise/async 等,那么现在问题来了,既然 es6 出现的新的异步方式,那么和之前的定时器相比,那个异步先执行呢?
宏任务
(macro)task,可以理解为每段代码都是一个宏任务,没错 JS 的主程序也是宏任务。同时两个定时器异步的部分也是宏任务。
微任务
microtask,可以理解是在当前 task 执行结束后立即执行的任务。也就是在主程序执行完成之后立即执行的部分。es6+出现的 promise,async 都是微任务。在这里要记住一句话,微任务的优先级是高于宏任务的。
程序执行顺序
1、主程序
因为 js 是单线程的,同一时间只能有一段代码在执行,所以首先执行的就是 JS 的主程序。之前说主程序是宏任务,微任务优先级又比宏任务高,那为什么还先执行主程序这个宏任务呢?
这是因为:没有主程序去构建微任务,微任务又怎么会出现呢,没有微任务的出现,当然就去找到主程序这个宏任务了,所以优先级的说法没有错误。
2、检查是否有异步任务
当上一个任务执行完成之后,程序会去检索是否有微任务,需要执行,如果有,就会先执行微任务。没有微任务但有宏任务,执行宏任务。没有任务,代码不在执行。
3、微任务
微任务代码执行,和正常的 JS 代码执行没有区别,从上往下编译执行!!!执行完成之后,会跳回到第二步。
4、宏任务
宏任务代码执行,和正常的 JS 代码执行没有区别,从上往下编译执行!!!执行完成之后,会跳回到第二步。
解答题目
大家在做此类题目时,也可以像我一样,在一旁记录一个宏任务库、微任务库,按照上面的顺序一步一步来,准没错!!!
1、主程序 - async async1
程序声明异步 async 异步函数 async1,当一个函数未调用时,函数内容的代码是不会编译执行的,所以第一步并没有输出内容。
微任务:空
宏任务:空
2、主程序 - async async2
这里同样也只是定义 async 函数 async2, 所以这一步也没有输出内容
微任务:空
宏任务:空
3、主程序 - console.log
程序执行到了 console.log,不存在异步,所以直接执行,控制台输出 “js start”。
微任务:空
宏任务:空
4、主程序 - setTimeout
程序终于来到了第一个异步部分 setTimeout,这个单次定时器的定时为 0s,意思为立即执行,但是因为他是异步的,所以他并不会立即执行,而是等到所有的主程序和排在他之前的异步任务执行完成之后才会执行。这里会把他添加到宏任务队列。
微任务:空
宏任务:setTimeout
5、主程序 - async1()
程序执行到了 async1 的函数调用,不存在异步,所以程序会去编译并执行 async1 内部部分。
微任务:空
宏任务:setTimeout
5.1、async1 - console.log
async 异步函数是这样的,函数被调用时,程序会正常立即执行,但是当碰到 await 关键词时,await 下一行的语句会作为微任务加入到微任务队列中,await 后面跟着的部分也是会立即执行的。因此这个 console.log 会立即执行,控制台输出“async1 start”
微任务:空
宏任务:setTimeout
5.2、async1 - await async2()
await 语句后面的内容会立即执行,下一行和之后的内容会加入到微任务队列,所以又进入到 async2 里面,并在微任务队列加入一个微任务。参考:前端进阶面试题详细解答
微任务:async1
宏任务:setTimeout
5.2.1、async2 - console.log
console.log 是立即执行的,没有异步部分,所以控制台上输出“async2”。至此,async1 里面的同步内容执行完成了。
微任务:async1
宏任务:setTimeout
6、主程序 - Promise
promise 的异步是这样的,在构建实例时传入函数的内容,是立即编译执行的,后面的 then 会加入到微任务队列。
微任务:async1
宏任务:setTimeout
6.1、Promise - console.log
console.log 立即执行,没有异步部分。控制台输出“primise”。
微任务:async1
宏任务:setTimeout
6.2、Promise - resolve
resolve 立即执行,但是后面的 then 加入微任务队列。
微任务:async1 Promise-then
宏任务:setTimeout
7、主程序 - console.log
console.log 立即执行,没有异步部分。控制台输出“js end”。至此,主程序已经执行完成,接下来会执行上述的第二步。
微任务:async1 Promise-then
宏任务:setTimeout
8、微任务 async1 - console.log
监测到有微任务,执行微任务。console.log 立即执行,没有异步部分。控制台输出“async1 end”。同时删除微任务队列中对应的任务,再次回到第二步。
微任务:Promise-then
宏任务:setTimeout
9、微任务 Promise-then - console.log
监测到有微任务,执行微任务。console.log 立即执行,没有异步部分。控制台输出“then”。同时删除微任务队列中对应的任务,再次回到第二步。
微任务:空
宏任务:setTimeout
8 和 9 两步,在不同的浏览器版本可能执行的顺序并不相同,所以不必纠结这两部前后顺序问题。
10、宏任务 setTimeout - console.log
监测到没有微任务,执行宏任务队列。console.log 立即执行,没有异步部分。控制台输出“timeout”。同时删除宏任务队列中对应的任务,再次回到第二步。
微任务:空
宏任务:空
11、执行结束
检测到任务队列都已执行完成,代码执行结束。
总结
至此、面试题就已经讲解完成了,内容比较长,但是也比较细,各位碰到类似题目时,可以按照我的方法去做一下,基本不会再出错了。
评论