javascript之事件循环、宏任务、微任务

任务队列原理

  • js是单线程机制,事件循环是惟一的,可是任务队列能够拥有多个html

  • 任务队列分为两种:macroTask(宏任务)与microTask(微任务)。在最新标准中,它们被分别称为task与jobshtml5

  • macroTask大概包括:script(总体代码)、setTimeout、setInterval、 setImmediate、I/O、UI renderingnode

  • microTask大概包括: process.nextTick、Promise、 Object.observe(已废弃)、MutationObserver(html5新特性)promise

  • setTimeout/Promise等咱们称之为任务源。而进入任务队列的是他们指定的具体执行任务bash

  • 来自不一样任务源的任务会进入到不一样的任务队列。其中setTimeout与setInterval是同源的函数

  • 其中每个任务的执行,不管是macroTask仍是microTask,都是借助函数调用栈来完成oop

从script(总体代码)开始第一次循环,以后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),而后执行全部的microTask。当全部可执行的microTask执行完毕以后。循环再次从macroTask开始,找到其中一个任务队列执行完毕,而后再执行全部的microTask,这样一直循环下去。post

script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI renderingui

问题引入

setTimeout(function(){console.log(1)},0);

console.log(2)
//2 1
复制代码

首先执行主线程中的同步任务,当主线程任务执行完以后,再从event loop中读取任务,所以先输出2,再输出1。spa

event loop读取任务的前后顺序,取决于任务队列(Job queue)中对于不一样任务读取规则的限定。

setTimeout(function () {
  console.log(4);
}, 0);

Promise.resolve().then(function () {
  console.log(2);
});
console.log(1);

//1 2 3
复制代码

先输出1,没有问题,由于是同步任务在主线程中优先执行,这里的问题是setTimeout和Promise.then任务的执行优先级是如何定义的?

任务队列中的执行顺序

任务队列分为:macroTask(宏任务)与microTask(微任务)。咱们来假设

macroTask队列包含任务: a1, a2 , a3

microTask队列包含任务: b1, b2 , b3

执行顺序为,首先执行marcoTask队列开头的任务,也就是 a1 任务,执行完毕后,在执行microTask队列里的全部任务,也就是依次执行b1, b2 , b3,执行完后清空microTask中的任务,接着执行marcoTask中的第二个任务,依次循环。

setTimeout(function(){
    console.log(1)
},0);

new Promise(function(resolve,reject){
   console.log(2);
   resolve();
}).then(function(){
    console.log(3)
}).then(function(){
    console.log(4)
});

process.nextTick(function(){
    console.log(5)
});

console.log(6);

//2 6 5 3 4 1
复制代码

script(主程序代码)——>process.nextTick——>promise——>setTimeout

  • 一、主体部分:定义promise的构造部分是同步的, 所以先输出2 ,主体部分再输出6,(同步任务,严格按照顺序执行)
  • 二、process.nextTick: 输出5
  • 三、这里的promise部分,严格的说实际上是promise.then部分,输出的是3,4
  • 四、setTimeout : 最后输出1

更复杂的例子

new Promise(function(resolve,reject){
   console.log(2);
   setTimeout(function(){
        resolve()
   },0)
}).then(function(){
    console.log(3)
}).then(function(){
    console.log(4)
});

setTimeout(function(){
    console.log(1)
},0);

process.nextTick(function(){
    console.log(5);
});

console.log(6);
//2 6 5 1 3 4
复制代码

区别在于Promise的构造中,没有同步的resolve,所以promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法,而这个resolve是在一个setTimout时间中完成的,所以3,4最后输出

任务队列执行顺序升级版

console.log('1');

setTimeout(function () {
    console.log('2');
    new Promise(function (resolve) {
        console.log('3');
        resolve();
    }).then(function () {
        console.log('4')
    }).then(function () {
        console.log('5')
    });
})

new Promise(function (resolve) {
    console.log('7');
    resolve();
}).then(function () {
    console.log('8')
})

setTimeout(function () {
    console.log('9');
    new Promise(function (resolve) {
        resolve();
    }).then(function () {
        console.log('10')
    }).then(function () {
        console.log('11')
    });

    new Promise(function (resolve) {
        resolve();
    }).then(function () {
        console.log('12')
    })
})
复制代码

一、第一轮事件循环流程分析以下:

  • 总体script做为第一个宏任务进入主线程,遇到console.log,输出1
  • 遇到setTimeout,其回调函数被分发到宏任务Event Queue中。咱们暂且记为setTimeout1
  • 遇到Promise,new Promise直接执行,输出7。then被分发到微任务Event Queue中。咱们记为then1
  • 又遇到了setTimeout,其回调函数被分发到宏任务Event Queue中,咱们记为setTimeout2
宏任务Event Queue 微任务Event Queue
setTimeout1 then1
setTimeout2

第一轮事件循环宏任务结束时各Event Queue的状况如上表,此时已经输出了1和7。清空微任务,执行then1,输出8。第一轮事件循环正式结束,这一轮的结果是输出1,7,8。

二、第二轮事件循环流分析:

从setTimeout1宏任务开始

  • 先输出2
  • new Promise当即执行输出3,then也分发到微任务Event Queue中,记为then2
宏任务Event Queue 微任务Event Queue
setTimeout2 then2

第二轮事件循环宏任务执行结束,执行微任务then2,输出4,then被分发到微任务Event Queue中,记为then3

宏任务Event Queue 微任务Event Queue
setTimeout2 then3

执行微任务then3,输出5。第二轮事件循环正式结束,这一轮的结果是输出2,3,4,5。

三、第三轮事件循环流分析:

从setTimeout2宏任务开始

  • 先输出9
  • 第一个new Promise当即执行,then也分发到微任务Event Queue中,记为then4
  • 第二个new Promise当即执行,then也分发到微任务Event Queue中,记为then5
宏任务Event Queue 微任务Event Queue
then4
then5

第二轮事件循环宏任务执行结束,执行微任务then4,输出10,then被分发到微任务Event Queue中,记为then6。执行微任务then5,输出12

宏任务Event Queue 微任务Event Queue
then6

执行微任务then6,输出11。第三轮事件循环正式结束,这一轮的结果是输出9,10,12,11。

整段代码,共进行了三次事件循环,完整的输出为1,7,8,2,3,4,5,9,10,12,11。(node环境执行结果会有些不同)

参考文章:

从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue

JS事件循环机制(event loop)之宏任务/微任务

相关文章
相关标签/搜索