瀏覽器/nodeJS中的EventLoop

williamslau發表於2019-03-01

大家都知道JS是一門單執行緒語言,也就意味著JS無法進行多執行緒,但是JS中非同步的概念完全可以模擬多執行緒,而且效果差不到哪去 #要完全理解非同步,就需要了解 JS 的執行核心——事件迴圈(event loop) 但是在瀏覽器中執行JS和在nodeJS中執行還有一些差別,接下來我們就來看看這些差別在哪裡

首先是瀏覽器中的EventLoop 在說瀏覽器EventLoop之前我們要先看看瀏覽器模型

瀏覽器模型.png

  • 使用者介面 包括位址列,書籤欄等那些東西
  • 瀏覽器引擎 在使用者介面,呈現引擎之間傳送指令
  • 渲染引擎 也被稱為呈現引擎
  • 請求(網路) 用於網路呼叫,比如HTTP請求
  • JS直譯器 用於解析和執行JS程式碼
  • UI(使用者介面後端) 用於繪製樣式 和JS共用一個執行緒
  • 資料儲存 瀏覽器需要儲存的一些資料 不如Cookie

JS和css公用一個執行緒是因為瀏覽器不會同時渲染JS和css,一般都會先渲染css再執行JS

瀏覽器中的微任務   then   messageChannel   mutationObersve

瀏覽器中的巨集任務   setTimeout   setInterval

程式碼呼叫先進堆疊,堆疊是程式碼的總執行站,堆疊整個執行的過程中會先將微任務,巨集任務放到相應的佇列中,事件提出來等待觸發,等到總執行站中的程式碼空了,會先看微任務佇列中有沒有,如果有就會放到總執行站中執行,然後在看巨集任務佇列中有沒有。

瀏覽器EventLoop.png

setTimeout(function () {
    console.log('setTimeout')
})
Promise.resolve().then(function () {
      console.log('promise')
});
console.log('堆疊');

// 執行結果:
// 堆疊
// promise
// setTimeout
複製程式碼

如果把微任務佇列放到堆疊中執行的時候又發現了巨集任務,會順便吧微任務中的巨集任務一起執行了

setTimeout(function () {
    console.log('setTimeout1')
    Promise.resolve().then(function () {
        console.log('promise')
    });
})
setTimeout(function () {
    console.log('setTimeout2');
});

// 執行結果:
// setTimeout1
// promise
// setTimeout2
複製程式碼

然後是nodeJS執行環境中的EventLoop nodeJS中的巨集任務和微任務在瀏覽器的基礎上有新增了幾個 微任務   then   nextTick   messageChannel   mutationObersve

巨集任務   setTimeout   setInterval   setImmediate、

node的呼叫順序中會比瀏覽器中的複雜一些

nodeJS EventLoop.png

看圖雖然複雜,但是我們只需要關心timers計時器階段,poll輪詢階段,check檢查階段(setImmediate回掉),clons關閉階段以及微任務佇列即可,因為處理網路,內部呼叫與我們們的巨集任務和微任務的執行沒有太大的關係 和瀏覽器執行環境不同的是微任務只會在階段轉化的時候才會呼叫,就是close關閉階段後再執行下一階段的時候

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

// 執行結果:
// nextTick
// setImmediate
複製程式碼

如果巨集任務執行的時候又發現微任務了,不會和瀏覽器一樣順便執行了,而是會將微任務再放到微任務佇列中,等待整個階段結束後,下一個階段開始的時候先執行完微任務佇列中的微任務

setTimeout(function () {
    console.log('setTimeout1')
    Promise.resolve().then(function () {
        console.log('promise')
    });
})
setTimeout(function () {
    console.log('setTimeout2');
});

// 執行結果:
// setTimeout1
// promise
// setTimeout2
複製程式碼

反之亦然

process.nextTick(function () {
    console.log(1)
    setImmediate(function () {
        console.log(2);
    })
})
setImmediate(function () {
    console.log(3);
    process.nextTick(function () {
        console.log(4)
    })
})

// 執行結果:
// 1
// 3
// 2
// 4
複製程式碼

timeout immediate 兩個誰先執行不一定 取決於node的執行時間

setTimeout(function () {
    console.log('setTimeout');
})
setImmediate(function () {
    console.log('setImmediate')
});

// 執行結果:
// setTimeout
// setImmediate
// 或者:
// setImmediate
// setTimeout
複製程式碼

但是加上i/o檔案操作以後就會先執行setImmediate,因為setImmediate在i/o檔案操作後面的那個階段執行,執行完setImmediate會在下一個階段的時候再執行setTimeout (timers 計時器執行階段)

let fs = require('fs');
fs.readFile('./1.txt', function () {
    console.log('fs');
    setTimeout(function () {
        console.log('setTimeout');
    })
    setImmediate(function () {
        console.log('setImmediate')
    })
});

// 執行結果
// fs
// setImmediate
// setTimeout
複製程式碼

相關文章