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)之宏任務/微任務

相關文章
相關標籤/搜索