最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 宏任务和微任务到底是什么?如何执行的?

    正文概述 掘金(a_靖)   2021-03-23   459

    先来一道常见的面试题:

     

    console.log('start')
    
    setTimeout(() => {
      console.log('setTimeout')
    }, 0)
    
    new Promise((resolve) => {
      console.log('promise')
      resolve()
    })
      .then(() => {
        console.log('then1')
      })
      .then(() => {
        console.log('then2')
      })
    
    console.log('end')
    

    应该不少同学都能答出来,结果为:

     

    start 
    promise
    end
    then1
    then2
    setTimeout
    

    这个就涉及到JavaScript事件轮询中的宏任务和微任务。那么,你能说清楚到底宏任务和微任务是什么?是谁发起的?为什么微任务的执行要先于宏任务呢?

    首先,我们需要先知道JS运行机制。

    JS运行机制

    概念1: JS是单线程执行

    ”JS是单线程的”指的是JS 引擎线程。

    概念2:宿主

    JS运行的环境。一般为浏览器或者Node。

    概念3:执行栈

    是一个存储函数调用的栈结构,遵循先进后出的原则。

     

    function foo() {
      throw new Error('error')
    }
    function bar() {
      foo()
    }
    bar()
    

    宏任务和微任务到底是什么?如何执行的?

    stack.jpg

    当开始执行 JS 代码时,首先会执行一个 main 函数,然后执行我们的代码。根据先进后出的原则,后执行的函数会先弹出栈,在图中我们也可以发现,foo 函数后执行,当执行完毕后就从栈中弹出了。

    概念4:Event Loop

    JS到底是怎么运行的呢?

    宏任务和微任务到底是什么?如何执行的?

    image

    Event Loop中,每一次循环称为tick,每一次tick的任务如下:

    • 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
    • 检查是否存在微任务,有则会执行至微任务队列为空;
    • 如果宿主为浏览器,可能会渲染页面;
    • 开始下一轮tick,执行宏任务中的异步代码(setTimeout等回调)。

    概念5:宏任务和微任务

    在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了Promise,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。

    所以,总结一下,两者区别为:

     宏任务(macrotask)微任务(microtask)
    谁发起的宿主(Node、浏览器)JS引擎具体事件1. script (可以理解为外层同步代码) 2. setTimeout/setInterval 3. UI rendering/UI事件 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js)1. Promise 2. MutaionObserver 3. Object.observe(已废弃;Proxy 对象替代) 4. process.nextTick(Node.js)谁先运行后运行先运行会触发新一轮Tick吗不会

    拓展 1:asyncawait是如何处理异步任务的?

    简单说,async是通过Promise包装异步任务。

    比如有如下代码:

     

    async function async1() {
      await async2()
      console.log('async1 end')
    }
    async function async2() {
      console.log('async2 end')
    }
    async1()
    

    改为ES5的写法:

     

    new Promise((resolve, reject) => {
      // console.log('async2 end')
      async2() 
      ...
    }).then(() => {
     // 执行async1()函数await之后的语句
      console.log('async1 end')
    })
    

    当调用 async1 函数时,会马上输出 async2 end,并且函数返回一个 Promise,接下来在遇到 await的时候会就让出线程开始执行 async1 外的代码(可以把 await 看成是让出线程的标志)。
    然后当同步代码全部执行完毕以后,就会去执行所有的异步代码,那么又会回到 await 的位置,去执行 then 中的回调。

    拓展 2:setTimeoutsetImmediate谁先执行?

    setImmediateprocess.nextTick为Node环境下常用的方法(IE11支持setImmediate),所以,后续的分析都基于Node宿主。

    Node.js是运行在服务端的js,虽然用到也是V8引擎,但由于服务目的和环境不同,导致了它的API与原生JS有些区别,其Event Loop还要处理一些I/O,比如新的网络连接等,所以与浏览器Event Loop不太一样。

    一般来说,setImmediate会在setTimeout之前执行,如下:

     

    console.log('outer');
    setTimeout(() => {
      setTimeout(() => {
        console.log('setTimeout');
      }, 0);
      setImmediate(() => {
        console.log('setImmediate');
      });
    }, 0);
    

    其执行顺序为:

    1. 外层是一个setTimeout,所以执行它的回调的时候已经在timers阶段了
    2. 处理里面的setTimeout,因为本次循环的timers正在执行,所以其回调其实加到了下个timers阶段
    3. 处理里面的setImmediate,将它的回调加入check阶段的队列
    4. 外层timers阶段执行完,进入pending callbacks,idle, prepare,poll,这几个队列都是空的,所以继续往下
    5. 到了check阶段,发现了setImmediate的回调,拿出来执行
    6. 然后是close callbacks,队列是空的,跳过
    7. 又是timers阶段,执行console.log('setTimeout')

    但是,如果当前执行环境不是timers阶段,就不一定了。。。。顺便科普一下Node里面对setTimeout的特殊处理:setTimeout(fn, 0)会被强制改为setTimeout(fn, 1)

    看看下面的例子:

     

    setTimeout(() => {
      console.log('setTimeout');
    }, 0);
    
    setImmediate(() => {
      console.log('setImmediate');
    });
    

    其执行顺序为:

    1. 遇到setTimeout,虽然设置的是0毫秒触发,但是被node.js强制改为1毫秒,塞入times阶段
    2. 遇到setImmediate塞入check阶段
    3. 同步代码执行完毕,进入Event Loop
    4. 先进入times阶段,检查当前时间过去了1毫秒没有,如果过了1毫秒,满足setTimeout条件,执行回调,如果没过1毫秒,跳过
    5. 跳过空的阶段,进入check阶段,执行setImmediate回调

    可见,1毫秒是个关键点,所以在上面的例子中,setImmediate不一定在setTimeout之前执行了。

    拓展 3:Promiseprocess.nextTick谁先执行?

    因为process.nextTick为Node环境下的方法,所以后续的分析依旧基于Node。

    process.nextTick() 是一个特殊的异步API,其不属于任何的Event Loop阶段。事实上Node在遇到这个API时,Event Loop根本就不会继续进行,会马上停下来执行process.nextTick(),这个执行完后才会继续Event Loop。

    所以,nextTickPromise同时出现时,肯定是nextTick先执行,原因是nextTick的队列比Promise队列优先级更高。

    拓展 4:应用场景 - Vue中的vm.$nextTick

    vm.$nextTick 接受一个回调函数作为参数,用于将回调延迟到下次DOM更新周期之后执行。

    这个API就是基于事件循环实现的。
    “下次DOM更新周期”的意思就是下次微任务执行时更新DOM,而vm.$nextTick就是将回调函数添加到微任务中(在特殊情况下会降级为宏任务)。

    因为微任务优先级太高,Vue 2.4版本之后,提供了强制使用宏任务的方法。

    小结

    下面是道加强版的考题,大家可以试一试。

     

    console.log('script start')
    
    async function async1() {
      await async2()
      console.log('async1 end')
    }
    async function async2() {
      console.log('async2 end')
    }
    async1()
    
    setTimeout(function() {
      console.log('setTimeout')
    }, 0)
    
    new Promise(resolve => {
      console.log('Promise')
      resolve()
    })
      .then(function() {
        console.log('promise1')
      })
      .then(function() {
        console.log('promise2')
      })
    
    console.log('script end')
    



    起源地下载网 » 宏任务和微任务到底是什么?如何执行的?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元