90%的前端开发都答不上来的js异步面试题

2022-12-15 07:40:30 浏览数 (1)

最近面试中碰到了一道关于JS执行顺序的题目,题目比较基础,但是如果对于JS不熟的话,还是容易答不上来。再次记录和分析此次面试题,希望对大家有所帮助。

代码语言:javascript复制
    async function async1() {
    console.log("async1 start");
    await async2();
    console.log("async1 end");
    }
    async function async2() {
        console.log("async2");
    }
    console.log("js start");
    setTimeout(function () {
        console.log("timeout");
    }, 0);
    async1();
    new Promise(function (resolve) {
        console.log("promise");
        resolve();
    }).then(function () {
        console.log("then");
    });
    console.log("js end");

话不多说,先上结果

代码语言:javascript复制
    // 控制台输出结果
    "js start"
    "async1 start"
    "async2"
    "promise"
    "js end"
    "async1 end"
    "then"
    "timeout"

宏任务 微任务

如果看官是个新手的话,看到上面的输出结果,肯定一脸懵逼的,但是没关系,看完这篇文章您就懂了。想完全明白上面这道题目,还需要了解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、执行结束

检测到任务队列都已执行完成,代码执行结束。

总结

至此、面试题就已经讲解完成了,内容比较长,但是也比较细,各位碰到类似题目时,可以按照我的方法去做一下,基本不会再出错了。

0 人点赞