宏任务与微任务

本文原创:liuwanhtml

在说微任务与宏任务以前咱们先说一下同步任务与异步任务的概念吧。bash

同步任务与异步任务

JavaScript语言的一大特色就是单线程,也就是说,同一个时间只能作一件事。单线程就意味着,全部任务须要排队,前一个任务结束,才会执行后一个任务。若是前一个任务耗时很长,后一个任务就不得不一直等着。网络

若是排队是由于计算量大,CPU忙不过来,倒也算了,可是不少时候CPU是闲着的,由于IO设备(输入输出设备)很慢(好比Ajax操做从网络读取数据),不得不等着结果出来,再往下执行。异步

JavaScript语言的设计者意识到,这时主线程彻底能够无论IO设备,挂起处于等待中的任务,先运行排在后面的任务。等到IO设备返回告终果,再回过头,把挂起的任务继续执行下去。async

因而,全部任务能够分红两种,一种是同步任务(synchronous),另外一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务能够执行了,该任务才会进入主线程执行。 具体来讲,异步执行的运行机制以下。(同步执行也是如此,由于它能够被视为没有异步任务的异步执行。)函数

  1. 全部同步任务都在主线程上执行,造成一个执行栈(execution context stack)。
  2. 主线程以外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的全部同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,因而结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的3。

以上摘自廖雪峰的博客 JavaScript 运行机制详解:再谈Event Loop.oop

问题

咱们先看一下下面的代码,而后思考一下输出的前后顺序post

setTimeout(() =>{
    console.log('1')
});

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

console.log('4');
复制代码

按照同步与异步的概念来看,输出顺序应该是二、四、一、3. 可是,打开控制台,输入代码,查看输出,顺序是这样的二、四、三、1,发生了什么? ui

我太难了
想知道发生了什么就继续往下看吧。

宏任务与微任务

除了广义的同步任务和异步任务,咱们对任务有更精细的定义,分为宏任务和微任务。spa

  1. 宏任务:包括总体代码script,setTimeout,setInterval;
  2. 微任务:Promise,process.nextTick

注:Promise当即执行,then函数分发到“microtask”队列,process.nextTick分发到“microtask”队列 js引擎会把宏任务和微任务放置两个“任务队列”中,分别是“macrotask”队列以及“microtask”队列。在执行异步任务时,先执行宏任务,而后在执行微任务。

因此如今解释一下上面问题中的输出顺序问题:

  1. 这段代码做为宏任务,进入主线程
  2. 遇到setTimeout,把它的回掉函数放置“macrotask”队列中,而后接着执行下面的代码
  3. 遇到Promise,new Promise会当即执行,因而输出2,其then函数会被放置“microtask”队列
  4. 遇到console.log('4')直接就执行了
  5. 总体代码script做为宏任务已经执行结束,判断“microtask”队列中是否有可执行的微任务(then函数),而后执行,输出3
  6. 至此,整个代码的第一轮循环结束了,要开始下一轮循环,先去查看“macrotask”队列,有setTimeout的回掉函数,而后执行,执行结束,输出1。
  7. 结束。

因此上述问题的输出顺序知道怎么肥死了吧? 盗一张事件循环,宏任务,微任务的关系图,以下:

在这里插入图片描述
图说明:进入总体代码(宏任务)后,开始第一次循环。接着执行全部的微任务。而后再次从宏任务开始,找到符合执行条件的一个宏任务执行完毕,再执行全部的微任务。

复习一下上面的知识点,咱们瞅一眼如下代码:

console.log('1');                           

setTimeout(() => {                         
    console.log('2');
    process.nextTick(() => {
        console.log('3');
    })
    setTimeout(() => {
        console.log('10')
        new Promise((resolve) => {
            console.log('11');
            resolve();
        }).then(() => {
            console.log('12')
        })
    })    
    new Promise((resolve) => {
        console.log('4');
        resolve();
    }).then(() => {
        console.log('5')
    })
})

process.nextTick(() => {
    console.log('6');
})

new Promise((resolve) => {        
    console.log('7');
    resolve();
}).then(() => {
    console.log('8')
    setTimeout(() => {
        console.log('9')
    })                 
})

console.log('10')
复制代码

大声说出答案吧:一、七、十、六、八、二、四、三、五、九、十、十一、12 好吧,咱们分析一下:

  1. 整段代码做为宏任务,进入主线程
  2. 遇到console.log('1'),当即执行,并向下执行
  3. 遇到setTimeout,把它的回掉函数fn1,放置“macrotask”队列中,接着执行下面的代码
  4. 遇到process.nextTick,把其回调函数fn2放置“microtask”队列
  5. 遇到Promise,new Promise会当即执行,因而输出7,其then函数fn3会被放置“microtask”队列
  6. 遇到console.log('10')直接就执行了
  7. 总体代码script做为宏任务已经执行结束,判断“microtask”队列中是否有可执行的微任务(fn2以及fn3),队列具体先进先出的特色,因此先执行fn2,输出6,而后执行fn3,输出8,里面包含setTimeout,把它的回调函数fn4放置“macrotask”队列中。
  8. 至此,整个代码的第一轮循环结束了,要开始下一轮循环。如今“macrotask”队列中有fn1fn4
  9. 先去查看“macrotask”队列,先执行fn1
  10. 执行fn1,遇到console.log('2'),就输出,遇到process.nextTick,将其回调函数fn5放置“microtask”队列,遇到setTimeout,把它的回掉函数fn6放置“macrotask”队列中,遇到Promise,new Promise会当即执行,因而输出4,其then函数fn7会被放置“microtask”队列,即这个宏任务执行完成。“macrotask”队列里面有fn4fn6
  11. 如今检查“microtask”队列,里面有fn5fn7,把里面的任务所有执行完毕, 先执行fn5,输出3,再执行fn7,输出5
  12. 至此,又一轮的循环结束了
  13. 再检查“macrotask”队列,里面有fn4fn6,执行fn4,输出9.
  14. 而如今的“microtask”队列是空的,再检查“macrotask”队列,有fn6
  15. 执行fn6,输出10,遇到new Promise,输出11,并把其回调函数fn8放置“microtask”队列,至此宏任务fn6结束,
  16. 检查“microtask”队列,并执行fn8,输出12,至此,“macrotask”队列以及“microtask”队列所有空了。
  17. 结束。

参考文章

相关文章
相关标签/搜索