瀏覽器的event loop和node的event loop

1.什麼是event loop

event loops也就是事件循環,它是爲了協調事件(event),用戶交互(user interaction),腳本(script),渲染(rendering),網絡(networking),用戶代理(user agent)的工做而產生的一個機制。javascript

2.JavaScript的運行機制

2.1 單線程的JavaScript

JavaScript語言的一大特色就是單線程,也就是說在同一時間只作同一件事。這是基於js的執行環境決定的,由於在瀏覽器中,有許多的dom操做,若是在同一時間操做一個dom,很容易形成混亂,因此爲了不發生同一時間操做同一dom的狀況,js選擇只用一個主線程執行代碼,來保證程序執行的一致性,單線程的特色也應用到了node中。java

2.2 JavaScript中的任務和隊列

JavaScript是單線程的,也就意味着全部任務須要排隊,前一個任務執行完,才能執行下一個任務,可是由於IO設備(輸入輸出設備)很慢(好比Ajax從網絡讀取數據),不得不等待結果返回以後才能繼續,這樣的執行效率很慢。 因而分紅了兩種任務來處理,同步任務和異步任務。 同步任務是指在主線程排隊的任務,只有前面的任務執行完以後才執行後面的任務。 異步任務指的是任務不進入主線程,而進入到一個任務隊列(task queue),主線程的任務能夠繼續日後執行,而在任務隊列裏的異步任務執行完會通知主線程。node

3.瀏覽器的event loop

3.1執行棧與事件隊列

當javascript代碼執行的時候會將不一樣的變量存於內存中的不一樣位置:堆(heap)和棧(stack)中來加以區分。其中,堆裏存放着一些對象。而棧中則存放着一些基礎類型變量以及對象的指針。當全部全部同步任務都在主線程上執行時,這些任務被排列在一個單獨的地方,造成一個執行棧promise

當瀏覽器js引擎解析這段代碼時,會將同步任務順序加入執行棧中依次執行,當遇到異步任務時並不會一直等待異步任務返回結果再執行後面的任務,而是將異步任務掛起,繼續執行同步任務,當異步任務返回結果時,將異步任務的回調事件加入到一個事件隊列(Task Queue)當中去,這個事件隊列裏的任務並不會當即執行,而是等同步任務所有執行完,再依次執行事件隊列裏的事件。瀏覽器

依次執行同步任務,完成後依次執行事件隊列,完成後再去執行同步任務,這樣造成了一個循環,就是事件循環(Event Loop)。bash

3.2宏任務(macro task)與微任務(micro task)

異步任務又分爲宏任務與微任務兩種,微任務並非老老實實的按照事件隊列的順序去執行,而是按照microTask—>macroTask的順序去執行,先執行完隊列中全部的microTask再去執行macroTask網絡

宏任務和微任務的分類dom

  • MacroTask: script(總體代碼), setTimeout, setInterval, setImmediate(node獨有), I/O, UI rendering異步

  • MicroTask: process.nextTick(node獨有), Promises, Object.observe(廢棄), MutationObserversocket

舉個例子
setTimeout(()=>{
    console.log(1)
})

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

執行結果是:3 2 1
這是由於事件循環的順序是:同步代碼=>微任務=>宏任務
複製代碼

4.node的event loop

  • timers: 這個階段執行定時器隊列中的回調如 setTimeout() 和 setInterval()。

  • I/O callbacks: 這個階段執行幾乎全部的回調。可是不包括close事件,定時器和setImmediate()的回調。

  • idle, prepare: 這個階段僅在內部使用,能夠沒必要理會。

  • poll: 等待新的I/O事件,node在一些特殊狀況下會阻塞在這裏。

  • check: setImmediate()的回調會在這個階段執行。

  • close callbacks: 例如socket.on('close', ...)這種close事件的回調。

event loop的每一次循環都須要依次通過上述的階段。 每一個階段都有本身的callback隊列,每當進入某個階段,都會從所屬的隊列中取出callback來執行,當隊列爲空或者被執行callback的數量達到系統的最大數量時,進入下一階段。這六個階段都執行完畢稱爲一輪循環。

舉個例子(1)
瀏覽器與Node執行順序的區別
setTimeout(()=>{
    console.log('timer1')

    Promise.resolve().then(function() {
        console.log('promise1')
    })
})

setTimeout(()=>{
    console.log('timer2')

    Promise.resolve().then(function() {
        console.log('promise2')
    })
})

複製代碼

瀏覽器輸出: time1 promise1 time2 promise2 由於promise是microtask,因此當第一個setTimeout執行完以後,先執行promise。

Node輸出: time1 time2 promise1 promise2 由於time1和time2都在timers階段,因此先執行timers,promise的回調被加入到了microtask隊列,等到timers階段執行完畢,在去執行microtask隊列。

舉個例子(2)
MicroTask隊列與MacroTask隊列
setTimeout(function () {
   console.log(1);
});
console.log(2);
process.nextTick(() => {
   console.log(3);
});
new Promise(function (resolve, rejected) {
   console.log(4);
   resolve()
}).then(res=>{
   console.log(5);
})
setImmediate(function () {
   console.log(6)
})
console.log('end');
複製代碼

node輸出的順序是 2 4 end 3 5 1 6 首先執行的是同步任務中的2 4 end,而後是microTask隊列中的process.nextTick:三、promise.then:5,最後是macroTask隊列中的setTimeout:一、setImmediate:6,因爲Timer優於Check階段,因此先1後6。

相關文章
相關標籤/搜索