大家都知道JS是一門單執行緒語言,也就意味著JS無法進行多執行緒,但是JS中非同步的概念完全可以模擬多執行緒,而且效果差不到哪去 #要完全理解非同步,就需要了解 JS 的執行核心——事件迴圈(event loop) 但是在瀏覽器中執行JS和在nodeJS中執行還有一些差別,接下來我們就來看看這些差別在哪裡
首先是瀏覽器中的EventLoop 在說瀏覽器EventLoop之前我們要先看看瀏覽器模型
- 使用者介面 包括位址列,書籤欄等那些東西
- 瀏覽器引擎 在使用者介面,呈現引擎之間傳送指令
- 渲染引擎 也被稱為呈現引擎
- 請求(網路) 用於網路呼叫,比如HTTP請求
- JS直譯器 用於解析和執行JS程式碼
- UI(使用者介面後端) 用於繪製樣式 和JS共用一個執行緒
- 資料儲存 瀏覽器需要儲存的一些資料 不如Cookie
JS和css公用一個執行緒是因為瀏覽器不會同時渲染JS和css,一般都會先渲染css再執行JS
瀏覽器中的微任務 then messageChannel mutationObersve
瀏覽器中的巨集任務 setTimeout setInterval
程式碼呼叫先進堆疊,堆疊是程式碼的總執行站,堆疊整個執行的過程中會先將微任務,巨集任務放到相應的佇列中,事件提出來等待觸發,等到總執行站中的程式碼空了,會先看微任務佇列中有沒有,如果有就會放到總執行站中執行,然後在看巨集任務佇列中有沒有。
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的呼叫順序中會比瀏覽器中的複雜一些
看圖雖然複雜,但是我們只需要關心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
複製程式碼