美文网首页让前端飞Web前端之路
宏任务和微任务到底是什么?

宏任务和微任务到底是什么?

作者: 娜姐聊前端 | 来源:发表于2020-07-26 19:07 被阅读0次

    先来一道常见的面试题:

    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 引擎线程和渲染线程,且两个线程互斥。

    概念2:执行栈

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

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

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

    概念3:宿主

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

    概念4:Event Loop

    JS到底是怎么运行的呢?

    image

    JS引擎常驻于内存中,等待宿主将JS代码或函数传递给它。
    也就是等待宿主环境分配宏观任务,反复等待 - 执行即为事件循环。

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

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

    概念5:宏任务和微任务

    ES6 规范中,microtask 称为 jobs,macrotask 称为 task
    宏任务是由宿主发起的,而微任务由JavaScript自身发起。

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

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

    宏任务(macrotask) 微任务(microtask)
    谁发起的 宿主(Node、浏览器) JS引擎
    具体事件 script、setTimeout、setInterval、I/O、UI rendering、postMessage、MessageChannel、setImmediate Promise、MutaionObserver、process.nextTick
    谁先运行 后运行 先运行
    会触发新一轮Tick吗 不会

    拓展: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 中的回调。

    小结

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

    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')
    

    相关文章

      网友评论

        本文标题:宏任务和微任务到底是什么?

        本文链接:https://www.haomeiwen.com/subject/nfcglktx.html