面试必考题 => 说出下面代码的执行顺序

348 阅读5分钟

本人前端小白一枚,只是整理一下自己的学习过程,第一次在掘金发表自己的文章,有问题欢迎大家及时指出

之前在年初,由于疫情原因,找工作耽搁了一阵,后来通过几次视频面试,发现了一道经常被问到的代码执行顺序的问题,其中少不了对promise的说明。这里我们来一起探讨一下

A C D M Q E F G I K J B L N P H O

Promise

什么是Promise呢,第一次用Promise是在公司项目中,为了实现异步嵌套的一个需求(具体需求记不太清了),在了解Promise之前,需要了解javascript的工作机制以及特点

javascript的单线程特性

是的,javascript是单线程语言,原因很简单:在前端开发的初期,前端项目主要的运行环境是浏览器,作为浏览器端的脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。所以导致了javascript是单线程的特性。既然是单线程语言,那么在任务执行时就需要对其进行排序了,只能是前一个任务执行完毕后,才会执行下一个任务。

但是,当前一个任务执行时间过长,就会造成浏览器端的卡顿,因为单线程的缘故,只能在前一个任务结束后再执行后一个任务,所以基于这个问题,javascript产生了一个任务队列的概念

任务队列

任务队列顾名思义,就是对任务进行排序,在javascript中,所有任务可以分为两种:同步任务和异步任务。

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务指的是,不进入主线程、而进入任务队列的任务

总结一下,任务的执行顺序是在主线程中进行同步任务,异步任务进入到任务队列中,当主线程中的同步任务执行完毕后,会在任务队列中寻找未执行的异步任务,并根据任务队列中的顺序依次传递到主线程中进行执行,循环上述操作,这就是常说的eventloop

宏任务和微任务

在了解了JavaScript任务执行顺序和任务队列后,又衍生出了宏任务和微任务的概念

这里举个例子:

银行每天都会有很多客户去办理各种业务,每一个客户想要办理的业务就是一个宏任务,当这个客户想要办理的业务结束了,突然有新的业务想要办理,例如:客户A取款后,又想要办一张信用卡,按照银行的业务办理流程,不会让客户A去重新排号,而是直接在当前办理业务中继续执行办理信用卡的业务,那么,办理信用卡的这个新增的业务就是微任务。

通过例子可以看出,在当前宏任务中,若有微任务存在,会先执行当前宏任务的微任务,当所有微任务执行完毕后,才会执行下一个宏任务

明确了这一点之后,可以看一下下面的一道面试题:

setTimeout(() => console.log(1))

new Promise(resolve => {
  resolve()
  console.log(2)
}).then(() => {
  console.log(3)
})

console.log(4)

输出结果为:2,4,1,3

分析一下这个代码:

首先,分析里面的代码,从上到下的主流程中,setTimeout为第一个宏任务,内部的匿名函数会放到任务队列中,随后走到new Promise中,执行内部的resolve和console.log(2),Promise.then()的方法会当做当前主流程的微任务放入微任务的队列中,随后代码继续执行,输出console.log(4)

主线程执行完了,首先先去微任务队列中寻找未执行的代码,找到了Promise.then方法,执行输出了console.log(3),微任务队列清空后,当前宏任务执行完毕,寻找下一个宏任务setTimeout,输出console.log(1)

看懂了么??-_-///

翻译成大白话解释一下:

整个主流程相当于银行柜员在处理业务,setTimeout的操作相当于去银行取号,取号的过程中,银行柜员同时在处理业务,new Promise为客户A(主流程)需要办理的业务,一共办理了console.log(2)和console.log(4)两个业务,在业务办理完成后,客户A临时添加了console.log(3)的业务,也就是Promise.then这个微任务,按照银行柜员的工作流程,不需要重新排号,所以直接处理了then这个业务,结束之后,客户A(主流程)没有了临时业务需要办理,这个客户的业务全部办理完成,开始处理下一个客户B(下一个宏任务)的业务,setTimeout开始执行,所以输出的结果为2,4,1,3

是不是好理解多了

通过这段代码可以继续拓展一下

setTimeout(() => console.log(1))

new Promise(resolve => {
  resolve()
  console.log(2)
}).then(() => {
    new Promise(resolve => {
        resolve()
        console.log(3)
    }).then(() => {
        console.log(4)
    })
    console.log(5)
})

console.log(6)

输出结果为: 2,6,3,5,4,1

简单解释一下,微任务then中添加了额外的微任务,当前微任务未执行完毕,会继续执行,换句话说,在当前微任务中添加了额外的微任务,当前主线程会将所有微任务全部执行完毕,才会执行setTimeout的宏任务

以上就是我对代码执行顺序的理解,个人见解,希望大家指出其中的不足,欢迎讨论和批评,最后留一个面试中被问过的问题,希望大家一起研究一下(答案放在引言上了……)

new Promise((resolve, reject) => {
  console.log("A");
  setTimeout(() => {
    console.log("B");
  }, 0);
  console.log("C");
  resolve();
  console.log("D");
})
  .then(() => {
    console.log("E");
    new Promise((resolve, reject) => {
      console.log("F");
      resolve();
      console.log("G");
    })
      .then(() => {
        setTimeout(() => {
          console.log("H");
        }, 0);
        console.log("I");
      })
      .then(() => {
        console.log("J");
      });
  })
  .then(() => {
    console.log("K");
  });
setTimeout(() => {
  console.log("L");
}, 0);
new Promise((resolve, reject) => {
  console.log("M");
  resolve();
}).then(() => {
  setTimeout(() => {
    new Promise((resolve, reject) => {
      console.log("N");
      resolve();
    })
      .then(() => {
        setTimeout(() => {
          console.log("O");
        }, 0);
      })
      .then(() => {
        console.log("P");
      });
  }, 0);
});
console.log("Q");