很自信地说,看完就手撕各种面试的宏任务,微任务

2,408 阅读7分钟

同步任务和异步任务

js的单线程任务被分成同步任务和异步任务。同步任务就是按部就班的执行代码,必须等到上一个任务完成,再进行下一个任务。就比如在alert()之后再疯狂console.log(),弹窗不关闭控制台是不会打印任何东西的。

如果所有的任务变为同步任务,那可会出大问题,就比如等待别的地方给你发送数据,你肯定得采用轮询机制,判断他有没有发,如果他一直不发,那就要一直等待他发,否则就不会执行下一步,毫无疑问这是不可行的。所以就有了异步任务的概念,将异步操作注册为回调函数,在告诉他等到他执行完了把结果告诉程序,程序再继续干别的事情。

以下为同步任务和异步任务的执行图解:

image.png

从这张图 我们可以得出来一个结论,同步任务永远先于异步任务执行。因为执行栈判断出该任务是同步任务,会直接执行,而异步任务会加入到任务队列(一种先进先出的数据结构)后,暂且不会执行。等到执行栈为空后,再看任务队列的首任务有没有事件回调,存在事件回调那就执行,执行完后移出任务队列(应该是肯定会有事件回调,不然异步了个寂寞),依次往复,直到任务队列为空。

来个小练习

setTimeout(()=>{console.log("异步任务"),0});
console.log("同步任务");

输出结果为

image.png

异步任务的进一步细分 宏任务与微任务

异步任务的进一步细分,就像去银行办业务一样,先要取号进行排号。
一般上边都会印着类似:“您的号码为XX,前边还有XX人。”之类的字样。

因为柜员同时职能处理一个来办理业务的客户,这时每一个来办理业务的人就可以认为是银行柜员的一个宏任务来存在的,当柜员处理完当前客户的问题以后,选择接待下一位,广播报号,也就是下一个宏任务的开始。
所以多个宏任务合在一起就可以认为说有一个任务队列在这,里边是当前银行中所有排号的客户。
任务队列中的都是已经完成的异步操作,而不是说注册一个异步任务就会被放在这个任务队列中,就像在银行中排号,如果叫到你的时候你不在,那么你当前的号牌就作废了,柜员会选择直接跳过进行下一个客户的业务处理,等你回来以后还需要重新取号

而且一个宏任务在执行的过程中,是可以添加一些微任务的,就像在柜台办理业务,你前边的一位老大爷可能在存款,在存款这个业务办理完以后,柜员会问老大爷还有没有其他需要办理的业务,这时老大爷想了一下:“最近P2P爆雷有点儿多,是不是要选择稳一些的理财呢”,然后告诉柜员说,要办一些理财的业务,这时候柜员肯定不能告诉老大爷说:“您再上后边取个号去,重新排队”。
所以本来快轮到你来办理业务,会因为老大爷临时添加的“理财业务”而往后推。
也许老大爷在办完理财以后还想 再办一个信用卡?或者 再买点儿纪念币
无论是什么需求,只要是柜员能够帮她办理的,都会在处理你的业务之前来做这些事情,这些都可以认为是微任务。 在当前的微任务没有执行完成时,是不会执行下一个宏任务的。

该片段转自 作者:Jiasm
链接:juejin.cn/post/684490…

触发宏任务的方式

  • script 中的代码块
  • setTimeout()
  • setInterval()
  • setImmediate() (非标准,IE 和 Node.js 中支持)
  • 注册事件

触发微任务的方式

为什么要有微任务

微任务的执行时机,晚于当前本轮事件循环的 Call Stack(调用栈) 中的代码(宏任务),早于事件处理函数和定时器的回调函数。

使用微任务的最主要原因简单归纳为:

  • 减少操作中用户可感知到的延迟
  • 确保任务顺序的一致性,即便当结果或数据是同步可用的
  • 批量操作的优化

同步任务,宏任务与微任务动画执行流程

console.log('global begin');
setTimeout(function timer1(){
    queueMicrotask(()={
    console.log('queueMicrotask')
    })
    console.log('timer1 invoke')
},1800)
console.log('global end')

动画1 高清.gif

毫无疑问 在执行顺序上,同步任务永远优先于异步任务。执行完同步任务后,由于setTimeout()会产生宏任务,该代码段会等待1800ms后放到任务队列中,等待执行栈(call stack)为空后,将回调的timer加入到执行栈(call stack),timer1中的queueMicrotask()会产生一个微任务(老爷子办完理财后要办信用卡),会等待本轮宏任务(老爷子的正事理财)执行完后执行。

由此可以清楚的得到执行结果。

eventloop中 同步,宏任务,微任务再复盘

image.png

同步任务毫无疑问先执行,按照代码段顺序会将异步任务放到任务队列中,并且秉持先微再宏的原则,入主线程执行。

含promise的代码段

new Promise(() => {}).then() ,我们来看这样一个Promise代码

前面的 new Promise() 这一部分是一个构造函数,这是一个同步任务

后面的 .then() 才是一个异步微任务,这一点是非常重要的

含async await的代码段

async/await本质上还是基于Promise的一些封装,而Promise是属于微任务的一种

所以在使用await关键字与Promise.then效果类似 await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。

要注意的是如果 await 的是 promise对象,await 会暂停 async 函数内后面的代码,先执行 async 函数外的同步代码(注意,promise 内的同步代码会先执行),等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果返回后,再继续执行 async 函数内后面的代码

做题家请做题(面试真题)

滴滴智能中台(11.26)前端实习 一面

setTimeout(function(){   console.log('1') }); 
 new Promise(function(resolve){ 
    console.log('2'); 
    for(var i = 0; i < 10000; i++)
    {     i == 99 && resolve();  } }).then(function(){   console.log('3') });  console.log('4');

答案:2 4 3 1

new Promise是一个构造函数,是同步任务,由无脑先同步原则,先输出2 ,4 同步任务执行完后,setTimeout的回调再宏任务队列,promise的then方法再微任务队列,秉承先微再宏原则,再输出3 1

小米前端实习面经

 //执行顺序
   async function async1() {
           console.log('async1 start')
           await async2();
           console.log('async1 end')
       }

       async function async2() {
           console.log('async2')
       }

       console.log('script start')

       setTimeout(function () {
           console.log('setTimeout')
       }, 0)

       async1();
       
       new Promise(
           function (resolve) {
               console.log('promise1')
               resolve();
           })
           .then(
               function () {
                   console.log('promise2')
               })
       console.log('script end')

答案:

image.png

解析:同步任务无脑原则 script start async1 start已经输出 setTimeout加入到宏任务队列

await相当于promise then 是微任务 执行async2 输出 aysnc2 注意的是await的是一个promise对象,所以先不执行async1函数内的其他内容,要完成其他所有的同步任务,那接下来就输出promise1,script end 执行完后 再执行async1函数的其他内容 输出aysnc1 end 紧接着执行promise的then 方法输出promise2 最后执行宏任务的回调 输出setTimeout