大家都知道JS是一門單線程語言,也就意味著JS無法進行多線程,但是JS中異步的概念完全可以模擬多線程,而且效果差不到哪去
要完全理解異步,就需要了解 JS 的運行核心——事件循環(huán)(event loop)
但是在瀏覽器中運行JS和在nodeJS中運行還有一些差別,接下來我們就來看看這些差別在哪里
首先是瀏覽器中的EventLoop
在說瀏覽器EventLoop之前我們要先看看瀏覽器模型

- 用戶界面 包括地址欄,書簽欄等那些東西
- 瀏覽器引擎 在用戶界面,呈現(xiàn)引擎之間傳送指令
- 渲染引擎 也被稱為呈現(xiàn)引擎
- 請求(網(wǎng)絡(luò)) 用于網(wǎng)絡(luò)調(diào)用,比如HTTP請求
- JS解釋器 用于解析和執(zhí)行JS代碼
- UI(用戶界面后端) 用于繪制樣式 和JS共用一個線程
- 數(shù)據(jù)存儲 瀏覽器需要保存的一些數(shù)據(jù) 不如Cookie
JS和css公用一個線程是因為瀏覽器不會同時渲染JS和css,一般都會先渲染css再執(zhí)行JS
瀏覽器中的微任務(wù)
??then
??messageChannel
??mutationObersve
瀏覽器中的宏任務(wù)
??setTimeout
??setInterval
代碼調(diào)用先進堆棧,堆棧是代碼的總執(zhí)行站,堆棧整個執(zhí)行的過程中會先將微任務(wù),宏任務(wù)放到相應(yīng)的隊列中,事件提出來等待觸發(fā),等到總執(zhí)行站中的代碼空了,會先看微任務(wù)隊列中有沒有,如果有就會放到總執(zhí)行站中執(zhí)行,然后在看宏任務(wù)隊列中有沒有。

setTimeout(function () {
console.log('setTimeout')
})
Promise.resolve().then(function () {
console.log('promise')
});
console.log('堆棧');
// 執(zhí)行結(jié)果:
// 堆棧
// promise
// setTimeout
如果把微任務(wù)隊列放到堆棧中執(zhí)行的時候又發(fā)現(xiàn)了宏任務(wù),會順便吧微任務(wù)中的宏任務(wù)一起執(zhí)行了
setTimeout(function () {
console.log('setTimeout1')
Promise.resolve().then(function () {
console.log('promise')
});
})
setTimeout(function () {
console.log('setTimeout2');
});
// 執(zhí)行結(jié)果:
// setTimeout1
// promise
// setTimeout2
然后是nodeJS運行環(huán)境中的EventLoop
nodeJS中的宏任務(wù)和微任務(wù)在瀏覽器的基礎(chǔ)上有新增了幾個
微任務(wù)
??then
??nextTick
??messageChannel
??mutationObersve
宏任務(wù)
??setTimeout
??setInterval
??setImmediate、
node的調(diào)用順序中會比瀏覽器中的復(fù)雜一些

看圖雖然復(fù)雜,但是我們只需要關(guān)心timers計時器階段,poll輪詢階段,check檢查階段(setImmediate回掉),clons關(guān)閉階段以及微任務(wù)隊列即可,因為處理網(wǎng)絡(luò),內(nèi)部調(diào)用與咱們的宏任務(wù)和微任務(wù)的執(zhí)行沒有太大的關(guān)系
和瀏覽器運行環(huán)境不同的是微任務(wù)只會在階段轉(zhuǎn)化的時候才會調(diào)用,就是close關(guān)閉階段后再執(zhí)行下一階段的時候
process.nextTick(function () {
console.log('nextTick')
});
setImmediate(function () {
console.log('setImmediate')
});
// 執(zhí)行結(jié)果:
// nextTick
// setImmediate
如果宏任務(wù)執(zhí)行的時候又發(fā)現(xiàn)微任務(wù)了,不會和瀏覽器一樣順便執(zhí)行了,而是會將微任務(wù)再放到微任務(wù)隊列中,等待整個階段結(jié)束后,下一個階段開始的時候先執(zhí)行完微任務(wù)隊列中的微任務(wù)
setTimeout(function () {
console.log('setTimeout1')
Promise.resolve().then(function () {
console.log('promise')
});
})
setTimeout(function () {
console.log('setTimeout2');
});
// 執(zhí)行結(jié)果:
// 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)
})
})
// 執(zhí)行結(jié)果:
// 1
// 3
// 2
// 4
timeout immediate 兩個誰先執(zhí)行不一定 取決于node的執(zhí)行時間
setTimeout(function () {
console.log('setTimeout');
})
setImmediate(function () {
console.log('setImmediate')
});
// 執(zhí)行結(jié)果:
// setTimeout
// setImmediate
// 或者:
// setImmediate
// setTimeout
但是加上i/o文件操作以后就會先執(zhí)行setImmediate,因為setImmediate在i/o文件操作后面的那個階段執(zhí)行,執(zhí)行完setImmediate會在下一個階段的時候再執(zhí)行setTimeout (timers 計時器執(zhí)行階段)
let fs = require('fs');
fs.readFile('./1.txt', function () {
console.log('fs');
setTimeout(function () {
console.log('setTimeout');
})
setImmediate(function () {
console.log('setImmediate')
})
});
// 執(zhí)行結(jié)果
// fs
// setImmediate
// setTimeout