JS事件循環

歡迎你們關注個人 githubhtml

1、爲何JS是單線程

是由JS的用途決定的,其用途主要是與用戶互動,以及操做DOM,若是不是單線程的話會帶來很複雜的同步問題。好比:若是是多線程的話,一個用戶新建一個DOM,另外一個用戶刪除同一個DOM,該如何處理將變得很是麻煩。html5

在html5中提出了web worker(todo 改天添加web worker的講解)標準,JS能夠建立多個線程或者是iframe,可是其餘子線程主要是用來進行JS計算不能操做DOM,且受到子線程的控制。因此也並無改變JS單線程的本質git

2、同步和異步

因爲是單線程,全部任務須要排隊,可是若是隊列中全部的任務都是同步的話會形成資源的浪費。github

因而任務分爲兩類:同步任務和異步任務。web

異步任務的過程:segmentfault

主線程(函數調用棧)發起一個異步請求,相應的工做線程接收請求,並告知主線程已經收到,主線程繼續執行後面的同步代碼,同時工做線程執行異步任務,工做線程完成工做後,通知主線程,主線程收到通知後,執行回調函數
複製代碼

3、宏任務和微任務

宏任務(macro-task)主要是:script代碼段、setTimeout、setInterval、Promise的構造函數是同步的、setImmediate、I/O、UIrenderingpromise

微任務(micro-task)主要是:Promise的回調、process.nextTick多線程

4、任務隊列和事件循環

瞭解任務隊列以前,先了解一下任務源,咱們將發起異步任務的稱之爲任務源(setTimeout、Promise等),進入任務隊列的是他們指定的任務。異步

在一個線程中,事件循環是惟一的,任務隊列是多個的。來自不一樣任務源的隊列進入到不一樣的任務隊列,setTimeout和setInterval是同源的函數

事件循環的步驟:

一、運行主線程(函數調用棧)中的同步任務
二、主線程(函數調用棧)執行到任務源時,通知相應的webAPIs進行相應的執行異步任務,將任務源指定的異步任務放入任務隊列中
三、主線程(函數調用棧)中的任務執行完畢後,而後執行全部的微任務,再執行宏任務,找到一個任務隊列執行完畢,再執行全部的微任務
四、不斷執行第三步
複製代碼

事件循環:指主線程重複從任務隊列中取消息,執行的過程

先來一個簡單的例子:

setTimeout(() => {
    console.log('begin')
})

new Promise((resolve) => {
    console.log('promise begin')
    for(let i = 0; i < 1000; i++) {
        i == 999 && resolve()
    }
}).then(() => {
    console.log('then begin')
})

console.log('end')
複製代碼

由於promise的構造函數是同步的,promise.then是異步的微任務,因此promise beigin先於end

根據上面對宏任務和微任務的分析,其輸出的狀況爲【promise begin——end——then begin——begin】

再來一個複雜點的,咱們來一步一步的分析一個例子來看:

console.log('golb1');

setTimeout(function() {
    console.log('timeout1');
    process.nextTick(function() {
        console.log('timeout1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')
    })
})

setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})

process.nextTick(function() {
    console.log('glob1_nextTick');
})

new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})

setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})

process.nextTick(function() {
    console.log('glob2_nextTick');
})

new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})

setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})
複製代碼

1、第一步、首先執行宏任務script。全局入棧。輸出glob1

2、遇到setTimeout,做爲任務源,將指定的任務加入宏任務隊列

3、遇到setImmediate,做爲任務源,將指定的任務加入宏任務隊列

4、遇到process.nextTick,做爲任務源,將指定的任務加入微任務隊列

5、遇到Promise的構造函數,進入執行棧,輸出glob1_promise,Promise.then()做爲任務源,將指定的任務加入微任務

6、遇到setTimeout,做爲任務源,將指定的任務加入宏任務隊列

7、遇到process.nextTick,做爲任務源,將指定的任務加入微任務隊列

8、遇到Promise的構造函數,進入執行棧,輸出glob2_promise,Promise.then()做爲任務源,將指定的任務加入微任務

9、遇到setImmediate,做爲任務源,將指定的任務加入宏任務隊列

10、執行全部微任務隊列,輸出glob1_nextTick和glob2_nextTick、glob1_then、glob2_then

11、循環執行宏任務隊列,執行setTimeout隊列、因爲遇到promise、process.nextTick等任務源,將新的微任務添加到隊列中,而後執行完setTimeout隊列後,再將剛添加進去的微任務所有執行,而後再執行setImmediate,依次循環下去

事件機制其實就是異步任務的通知機制



參考資料:

segmentfault.com/a/119000000…

www.ruanyifeng.com/blog/2014/1…

zhuanlan.zhihu.com/p/33127885

segmentfault.com/a/119000001…

www.cnblogs.com/nullcc/p/58…

相關文章
相關標籤/搜索