者: 五月君 來源:編程界|
事件循環(huán)是一種控制應(yīng)用程序的運(yùn)行機(jī)制,在不同的運(yùn)行時(shí)環(huán)境有不同的實(shí)現(xiàn),上一節(jié)講了瀏覽器中的事件循環(huán),它們有很多相似的地方,也有著各自的特點(diǎn),本節(jié)討論下 Node.js 中的事件循環(huán)。
Node.js 做為 JavaScript 的服務(wù)端運(yùn)行時(shí),主要與網(wǎng)絡(luò)、文件打交道,沒有了瀏覽器中事件循環(huán)的渲染階段。
在瀏覽器中有 HTML 規(guī)范來定義事件循環(huán)的處理模型,之后由各瀏覽器廠商實(shí)現(xiàn)。Node.js 中事件循環(huán)的定義與實(shí)現(xiàn)均來自于 Libuv。
Libuv 圍繞事件驅(qū)動(dòng)的異步 I/O 模型而設(shè)計(jì),最初是為 Node.js 編寫的,提供了一個(gè)跨平臺(tái)的支持庫。下圖展示了它的組成部分,Network I/O 是網(wǎng)絡(luò)處理相關(guān)的部分,右側(cè)還有文件操作、DNS,底部 epoll、kqueue、event ports、IOCP 這些是底層不同操作系統(tǒng)的實(shí)現(xiàn)。
圖片來源:http://docs.libuv.org/en/v1.x/_images/architecture.png
當(dāng) Node.js 啟動(dòng)時(shí),它會(huì)初始化事件循環(huán),處理提供的腳本,同步代碼入棧直接執(zhí)行,異步任務(wù)(網(wǎng)絡(luò)請求、文件操作、定時(shí)器等)在調(diào)用 API 傳遞回調(diào)函數(shù)后會(huì)把操作轉(zhuǎn)移到后臺(tái)由系統(tǒng)內(nèi)核處理。目前大多數(shù)內(nèi)核都是多線程的,當(dāng)其中一個(gè)操作完成時(shí),內(nèi)核通知 Node.js 將回調(diào)函數(shù)添加到輪詢隊(duì)列中等待時(shí)機(jī)執(zhí)行。
下圖左側(cè)是 Node.js 官網(wǎng)對(duì)事件循環(huán)過程的描述,右側(cè)是 Libuv 官網(wǎng)對(duì) Node.js 的描述,都是對(duì)事件循環(huán)的介紹,不是所有人上來都能去看源碼的,這兩個(gè)文檔通常也是對(duì)事件循環(huán)更直接的學(xué)習(xí)參考文檔,在 Node.js 官網(wǎng)介紹的也還是挺詳細(xì)的,可以做為一個(gè)參考資料學(xué)習(xí)。
左側(cè) Node.js 官網(wǎng)展示的事件循環(huán)分為 6 個(gè)階段,每個(gè)階段都有一個(gè) FIFO(先進(jìn)先出)隊(duì)列執(zhí)行回調(diào)函數(shù),這幾個(gè)階段之間執(zhí)行的優(yōu)先級(jí)順序還是明確的。
右側(cè)更詳細(xì)的描述了,在事件循環(huán)迭代前,先去判斷循環(huán)是否處于活動(dòng)狀態(tài)(有等待的異步 I/O、定時(shí)器等),如果是活動(dòng)狀態(tài)開始迭代,否則循環(huán)將立即退出。
下面對(duì)每個(gè)階段分別討論。
首先事件循環(huán)進(jìn)入定時(shí)器階段,該階段包含兩個(gè) API setTimeout(cb, ms)、setInterval(cb, ms) 前一個(gè)是僅執(zhí)行一次,后一個(gè)是重復(fù)執(zhí)行。
這個(gè)階段檢查是否有到期的定時(shí)器函數(shù),如果有則執(zhí)行到期的定時(shí)器回調(diào)函數(shù),和瀏覽器中的一樣,定時(shí)器函數(shù)傳入的延遲時(shí)間總比我們預(yù)期的要晚,它會(huì)受到操作系統(tǒng)或其它正在運(yùn)行的回調(diào)函數(shù)的影響。
例如,下例我們設(shè)置了一個(gè)定時(shí)器函數(shù),并預(yù)期在 1000 毫秒后執(zhí)行。
const now = Date.now();
setTimeout(function timer1(){
log(`delay ${Date.now() - now} ms`);
}, 1000);
setTimeout(function timer2(){
log(`delay ${Date.now() - now} ms`);
}, 5000);
someOperation();
function someOperation() {
// sync operation...
while (Date.now() - now < 3000) {}
}
當(dāng)調(diào)用 setTimeout 異步函數(shù)后,程序緊接著執(zhí)行了 someOperation() 函數(shù),中間有些耗時(shí)操作大約消耗 3000ms,當(dāng)完成這些同步操作后,進(jìn)入一次事件循環(huán),首先檢查定時(shí)器階段是否有到期的任務(wù),定時(shí)器的腳本是按照 delay 時(shí)間升序存儲(chǔ)在堆內(nèi)存中,首先取出超時(shí)時(shí)間最小的定時(shí)器函數(shù)做檢查,如果 **nowTime - timerTaskRegisterTime > delay** 取出回調(diào)函數(shù)執(zhí)行,否則繼續(xù)檢查,當(dāng)檢查到一個(gè)沒有到期的定時(shí)器函數(shù)或達(dá)到系統(tǒng)依賴的最大數(shù)量限制后,轉(zhuǎn)移到下一階段。
在我們這個(gè)示例中,假設(shè)執(zhí)行完 someOperation() 函數(shù)的當(dāng)前時(shí)間為 T + 3000:
定時(shí)器階段完成后,事件循環(huán)進(jìn)入到 pending callbacks 階段,在這個(gè)階段執(zhí)行上一輪事件循環(huán)遺留的 I/O 回調(diào)。根據(jù) Libuv 文檔的描述:大多數(shù)情況下,在輪詢 I/O 后立即調(diào)用所有 I/O 回調(diào),但是,某些情況下,調(diào)用此類回調(diào)會(huì)推遲到下一次循環(huán)迭代。聽完更像是上一個(gè)階段的遺留。
idle, prepare 階段是給系統(tǒng)內(nèi)部使用,idle 這個(gè)名字很迷惑,盡管叫空閑,但是在每次的事件循環(huán)中都會(huì)被調(diào)用,當(dāng)它們處于活動(dòng)狀態(tài)時(shí)。這一塊的資料介紹也不是很多。略...
poll 是一個(gè)重要的階段,這里有一個(gè)概念觀察者,有文件 I/O 觀察者,網(wǎng)絡(luò) I/O 觀察者等,它會(huì)觀察是否有新的請求進(jìn)入,包含讀取文件等待響應(yīng),等待新的 socket 請求,這個(gè)階段在某些情況下是會(huì)阻塞的。
在阻塞 I/O 之前,要計(jì)算它應(yīng)該阻塞多長時(shí)間,參考 Libuv 文檔上的一些描述,以下這些是它計(jì)算超時(shí)時(shí)間的規(guī)則:
如果以上情況都沒有,則采用最近定時(shí)器的超時(shí)時(shí)間,或者如果沒有活動(dòng)的定時(shí)器,則超時(shí)時(shí)間為無窮大,poll 階段會(huì)一直阻塞下去。
很簡單的一段代碼,我們啟動(dòng)一個(gè) Server,現(xiàn)在事件循環(huán)的其它階段沒有要處理的任務(wù),它會(huì)在這里等待下去,直到有新的請求進(jìn)來。
const http = require('http');
const server = http.createServer();
server.on('request', req => {
console.log(req.url);
})
server.listen(3000);
結(jié)合階段一的定時(shí)器,在看個(gè)示例,首先啟動(dòng) app.js 做為服務(wù)端,模擬延遲 3000ms 響應(yīng),這個(gè)只是為了配合測試。再運(yùn)行 client.js 看下事件循環(huán)的執(zhí)行過程:
// client.js
const now = Date.now();
setTimeout(() => log(`setTimeout run after ${Date.now() - now} ms`), 1000);
someAsyncOperation();
function someAsyncOperation() {
http.get('http://localhost:3000/api/news', () => {
log(`fetch data success after ${Date.now() - now} ms`);
});
}
// app.js
const http = require('http');
http.createServer((req, res) => {
setTimeout(() => { res.end('OK!') }, 3000);
}).listen(3000);
當(dāng) poll 階段隊(duì)列為空時(shí),并且腳本被 setImmediate() 調(diào)度過,此時(shí),事件循環(huán)也會(huì)結(jié)束 poll 階段,進(jìn)入下一個(gè)階段 check。
check 階段在 poll 階段之后運(yùn)行,這個(gè)階段包含一個(gè) API setImmediate(cb) 如果有被 setImmediate 觸發(fā)的回調(diào)函數(shù),就取出執(zhí)行,直到隊(duì)列為空或達(dá)到系統(tǒng)的最大限制。
拿 setTimeout 和 setImmediate 對(duì)比,這是一個(gè)常見的例子,基于被調(diào)用的時(shí)機(jī)和定時(shí)器可能會(huì)受到計(jì)算機(jī)上其它正在運(yùn)行的應(yīng)用程序影響,它們的輸出順序,不總是固定的。
setTimeout(() => log('setTimeout'));
setImmediate(() => log('setImmediate'));
// 第一次運(yùn)行
setTimeout
setImmediate
// 第二次運(yùn)行
setImmediate
setTimeout
但是一旦把這兩個(gè)函數(shù)放入一個(gè) I/O 循環(huán)內(nèi)調(diào)用,setImmediate 將總是會(huì)被優(yōu)先調(diào)用。因?yàn)?setImmediate 屬于 check 階段,在事件循環(huán)中總是在 poll 階段結(jié)束后運(yùn)行,這個(gè)順序是確定的。
fs.readFile(__filename, () => {
setTimeout(() => log('setTimeout'));
setImmediate(() => log('setImmediate'));
})
在 Libuv 中,如果調(diào)用關(guān)閉句柄 uv_close(),它將調(diào)用關(guān)閉回調(diào),也就是事件循環(huán)的最后一個(gè)階段 close callbacks。
這個(gè)階段的工作更像是做一些清理工作,例如,當(dāng)調(diào)用 socket.destroy(),'close' 事件將在這個(gè)階段發(fā)出,事件循環(huán)在執(zhí)行完這個(gè)階段隊(duì)列里的回調(diào)函數(shù)后,檢查循環(huán)是否還 alive,如果為 no 退出,否則繼續(xù)下一次新的事件循環(huán)。
在瀏覽器的事件循環(huán)中,把任務(wù)劃分為 Task、Microtask,在 Node.js 中是按照階段劃分的,上面我們介紹了 Node.js 事件循環(huán)的 6 個(gè)階段,給用戶使用的主要是 timer、poll、check、close callback 四個(gè)階段,剩下兩個(gè)由系統(tǒng)內(nèi)部調(diào)度。這些階段所產(chǎn)生的任務(wù),我們可以看做 Task 任務(wù)源,也就是常說的 “Macrotask 宏任務(wù)”。
通常我們在談?wù)撘粋€(gè)事件循環(huán)時(shí)還會(huì)包含 Microtask,Node.js 里的微任務(wù)有 Promise、還有一個(gè)也許很少關(guān)注的函數(shù) queueMicrotask,它是在 Node.js v11.0.0 之后被實(shí)現(xiàn)的,參見 PR/22951。
Node.js 中的事件循環(huán)在每一個(gè)階段執(zhí)行后,都會(huì)檢查微任務(wù)隊(duì)列中是否有待執(zhí)行的任務(wù)。
Node.js 在 v11.x 前后,每個(gè)階段如果即存在可執(zhí)行的 Task 又存在 Microtask 時(shí),會(huì)有一些差異,先看一段代碼:
setImmediate(() => {
log('setImmediate1');
Promise.resolve('Promise microtask 1')
.then(log);
});
setImmediate(() => {
log('setImmediate2');
Promise.resolve('Promise microtask 2')
.then(log);
});
在 Node.js v11.x 之前,當(dāng)前階段如果存在多個(gè)可執(zhí)行的 Task,先執(zhí)行完畢,再開始執(zhí)行微任務(wù)。基于 v10.22.1 版本運(yùn)行結(jié)果如下:
setImmediate1
setImmediate2
Promise microtask 1
Promise microtask 2
在 Node.js v11.x 之后,當(dāng)前階段如果存在多個(gè)可執(zhí)行的 Task,先取出一個(gè) Task 執(zhí)行,并清空對(duì)應(yīng)的微任務(wù)隊(duì)列,再次取出下一個(gè)可執(zhí)行的任務(wù),繼續(xù)執(zhí)行。基于 v14.15.0 版本運(yùn)行結(jié)果如下:
setImmediate1
Promise microtask 1
setImmediate2
Promise microtask 2
在 Node.js v11.x 之前的這個(gè)執(zhí)行順序問題,被認(rèn)為是一個(gè)應(yīng)該要修復(fù)的 Bug 在 v11.x 之后并修改了它的執(zhí)行時(shí)機(jī),和瀏覽器保持了一致,詳細(xì)參見 issues/22257 討論。
Node.js 中還有一個(gè)異步函數(shù) process.nextTick(),從技術(shù)上講它不是事件循環(huán)的一部分,它在當(dāng)前操作完成后處理。如果出現(xiàn)遞歸的 process.nextTick() 調(diào)用,這將會(huì)很糟糕,它會(huì)阻斷事件循環(huán)。
如下例所示,展示了一個(gè) process.nextTick() 遞歸調(diào)用示例,目前事件循環(huán)位于 I/O 循環(huán)內(nèi),當(dāng)同步代碼執(zhí)行完成后 process.nextTick() 會(huì)被立即執(zhí)行,它會(huì)陷入無限循環(huán)中,與同步的遞歸不同的是,它不會(huì)觸碰 v8 最大調(diào)用堆棧限制。但是會(huì)破壞事件循環(huán)調(diào)度,setTimeout 將永遠(yuǎn)得不到執(zhí)行。
fs.readFile(__filename, () => {
process.nextTick(() => {
log('nextTick');
run();
function run() {
process.nextTick(() => run());
}
});
log('sync run');
setTimeout(() => log('setTimeout'));
});
// 輸出
sync run
nextTick
將 process.nextTick 改為 setImmediate 雖然是遞歸的,但它不會(huì)影響事件循環(huán)調(diào)度,setTimeout 在下一次事件循環(huán)中被執(zhí)行。
fs.readFile(__filename, () => {
process.nextTick(() => {
log('nextTick');
run();
function run() {
setImmediate(() => run());
}
});
log('sync run');
setTimeout(() => log('setTimeout'));
});
// 輸出
sync run
nextTick
setTimeout
process.nextTick 是立即執(zhí)行,setImmediate 是在下一次事件循環(huán)的 check 階段執(zhí)行。但是,它們的名字著實(shí)讓人費(fèi)解,也許會(huì)想這兩個(gè)名字交換下比較好,但它屬于遺留問題,也不太可能會(huì)改變,因?yàn)檫@會(huì)破壞 NPM 上大部分的軟件包。
在 Node.js 的文檔中也建議開發(fā)者盡可能的使用 setImmediate(),也更容易理解。
Node.js 事件循環(huán)分為 6 個(gè)階段,每個(gè)階段都有一個(gè) FIFO(先進(jìn)先出)隊(duì)列執(zhí)行回調(diào)函數(shù),這幾個(gè)階段之間執(zhí)行的優(yōu)先級(jí)順序還是明確的。
事件循環(huán)的每一個(gè)階段,有時(shí)還會(huì)伴隨著一些微任務(wù)而運(yùn)行,這里以 Node.js v11.x 版本為分界線會(huì)有一些差異,文中也都有詳細(xì)的介紹。
在上一篇介紹了瀏覽器的事件循環(huán)機(jī)制,本篇又詳細(xì)的介紹了 Node.js 中的事件循環(huán)機(jī)制,留給大家一個(gè)思考問題,結(jié)合自己的理解,總結(jié)下瀏覽器與 Node.js 中事件循環(huán)的一些差異,這個(gè)也是常見的一個(gè)面試題,歡迎在留言區(qū)討論。
在 Cnode 上看到的兩篇事件循環(huán)相關(guān)文章,推薦給大家,文章很精彩,評(píng)論也更加精彩。
Reference
http://docs.libuv.org/en/v1.x/design.html
https://nodejs.org/zh-cn/docs/guides/event-loop-timers-and-nexttick
avaScript 語言中的 for 循環(huán)用于多次執(zhí)行代碼塊,它是 JavaScript 中最常用的一個(gè)循環(huán)工具,還可用于數(shù)組的遍歷循環(huán)等。
我們?yōu)槭裁匆褂?for 循環(huán)呢?打個(gè)比方,例如我們想要控制臺(tái)輸出1到1000之間的所有數(shù)字,如果單寫輸出語句,要寫1000句代碼,但是如果使用 for 循環(huán),幾句代碼就能實(shí)現(xiàn)。總之,使用 for 循環(huán)能夠讓我們寫代碼更方便快捷(當(dāng)然啦,否則要它干嘛)。
語法如下所示:
for(變量初始化; 條件表達(dá)式; 變量更新) {
// 條件表達(dá)式為true時(shí)執(zhí)行的語句塊
}
例如我們在一個(gè)HTML文件中,編寫如下代碼,實(shí)現(xiàn)計(jì)算1到100的總和:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS_俠課島(9xkd.com)</title>
</head>
<body>
<script>
var result = 0;
for(var i = 1; i <= 100; i++) {
result = result + i;
}
alert(result);
</script>
</body>
</html>
在瀏覽器中打開這個(gè)文件,會(huì)彈出一個(gè)彈出層,彈出層中顯示的是1到100的總和:
上述代碼中,我們聲明了一個(gè)變量 result 并給它賦值為 0,表示初始的總和為 0 。
然后在 for 循環(huán)中三個(gè)語句:
此時(shí)我們可以一點(diǎn)點(diǎn)來看這個(gè) for 循環(huán):
第一次循環(huán): result = 0 + 1 // 此時(shí)result值為0, i的值為1
第二次循環(huán): result = 1 + 2 // 此時(shí)result值為0+1,i的值為2
第三次循環(huán): result = 3 + 3 // 此時(shí)result值為1+2,i的值為3
第四次循環(huán): result = 6 + 4 // 此時(shí)result值為3+3,i的值為4
第五次循環(huán): result = 10 + 5 // 此時(shí)result值為6+4,i的值為5
...
我們只需要搞清楚 for 循環(huán)中的執(zhí)行原理,不需要手動(dòng)來計(jì)算求和,只要寫好代碼,執(zhí)行代碼后計(jì)算機(jī)會(huì)很快會(huì)告訴我們1到 100 的總和。
再補(bǔ)充一下,上述代碼中result = result + i,我們也可以寫成 result += i,這是我們之前學(xué)過的加賦值運(yùn)算符,還記得嗎?
示例:
再來看一個(gè)例子,例如我們可以使用 for 循環(huán)來實(shí)現(xiàn)數(shù)組遍歷,首先定義一個(gè)數(shù)組 lst:
var lst = ["a", "b", "c", "d", "e"];
在寫 for 循環(huán)時(shí),首先就是要搞清楚小括號(hào)里面的三個(gè)語句,因?yàn)槲覀兛梢酝ㄟ^數(shù)組中元素的下標(biāo)索引來獲取元素的值,而數(shù)組的索引又是從 0 開始,所以變量初始化可以設(shè)置為i = 0。第二個(gè)條件表達(dá)式,因?yàn)閿?shù)組中最后一個(gè)索引為 lst.length - 1,所以只要小于等于 lst.length - 1,循環(huán)就會(huì)一直執(zhí)行。而i <= lst.length - 1 就相當(dāng)于 i<lst.length。第三個(gè)變量更新,當(dāng)循環(huán)每循環(huán)一次,索引值就加一,所以為 i++。
所以循環(huán)可以像下面這樣寫:
for(i = 0; i<lst.length; i++){
console.log(lst[i]); // 輸出數(shù)組中的元素值,從索引為0的值開始輸出,每次加1,一直到lst.length-1
}
輸出:
a
b
c
d
e
其實(shí)遍歷數(shù)組還有一種更好的方法,就是使用 for...in 循環(huán)語句來遍歷數(shù)組。
for...in 循環(huán)主要用于遍歷數(shù)組或?qū)ο髮傩裕瑢?duì)數(shù)組或?qū)ο蟮膶傩赃M(jìn)行循環(huán)操作。for...in 循環(huán)中的代碼每執(zhí)行一次,就會(huì)對(duì)數(shù)組的元素或者對(duì)象的屬性進(jìn)行一次操作。
語法如下:
for (變量 in 對(duì)象) {
// 代碼塊
}
for 循環(huán)括號(hào)內(nèi)的變量是用來指定變量,指定的可以是數(shù)組對(duì)象或者是對(duì)象屬性。
示例:
使用 for...in 循環(huán)遍歷我們定義好的 lst 數(shù)組:
var lst = ["a", "b", "c", "d", "e"];
for(var l in lst){
console.log(lst[l]);
}
輸出:
a
b
c
d
e
除了數(shù)組,for...in 循環(huán)還可以遍歷對(duì)象,例如我們遍歷 俠俠 的個(gè)人基本信息:
var object = {
姓名:'俠俠',
年齡:'22',
性別:'男',
出生日期:'1997-08-05',
職業(yè):'程序員',
特長:'跳舞'
}
for(var i in object) {
console.log(i + ":" + object[i]);
}
輸出:
姓名: 俠俠
年齡: 22
性別: 男
出生日期: 1997-08-05
職業(yè):程序員
特長:跳舞
avaScript 的 Event Loop(事件循環(huán))是 JavaScript 運(yùn)行時(shí)環(huán)境(如瀏覽器和 Node.js)的核心機(jī)制之一,它使得 JavaScript 能夠處理異步操作而不會(huì)阻塞程序的執(zhí)行。了解 Event Loop 對(duì)于理解 JavaScript 的非阻塞行為和編寫高效的異步代碼至關(guān)重要。
首先,重要的是要理解 JavaScript 是一種單線程的語言。這意味著 JavaScript 在同一時(shí)間內(nèi)只能執(zhí)行一個(gè)任務(wù)。然而,JavaScript 需要能夠處理各種異步操作(如 AJAX 請求、文件讀取、用戶交互等),這些操作可能會(huì)花費(fèi)很長時(shí)間完成。為了解決這個(gè)問題,JavaScript 采用了 Event Loop 和 Callback Queues(回調(diào)隊(duì)列)。
調(diào)用棧是 JavaScript 代碼執(zhí)行時(shí)的數(shù)據(jù)結(jié)構(gòu),用于存儲(chǔ)函數(shù)調(diào)用和返回地址。每當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),它就會(huì)被推入調(diào)用棧,并在函數(shù)執(zhí)行完畢后從棧中彈出。如果調(diào)用棧滿了(即達(dá)到了最大調(diào)用深度),則會(huì)發(fā)生棧溢出錯(cuò)誤。
堆是用于存儲(chǔ)對(duì)象、數(shù)組等引用類型的內(nèi)存區(qū)域。與調(diào)用棧不同,堆是動(dòng)態(tài)分配的,并且其大小不是固定的。
Web APIs 是瀏覽器提供的一組與瀏覽器功能交互的接口,如 DOM 操作、網(wǎng)絡(luò)請求等。這些 API 通常是異步的,并且它們有自己的線程或進(jìn)程來處理請求。
當(dāng)異步操作完成時(shí)(如 AJAX 請求、setTimeout、Promise 解決等),相應(yīng)的回調(diào)函數(shù)會(huì)被放入任務(wù)隊(duì)列(或稱為宏任務(wù)隊(duì)列)或微任務(wù)隊(duì)列中。任務(wù)隊(duì)列中的任務(wù)在當(dāng)前的執(zhí)行棧清空后才會(huì)被執(zhí)行,而微任務(wù)隊(duì)列中的任務(wù)會(huì)在當(dāng)前執(zhí)行棧清空后、但下一個(gè)宏任務(wù)執(zhí)行前立即執(zhí)行。
Event Loop 的工作流程可以概括為以下幾個(gè)步驟:
console.log('1');
setTimeout(() => {
console.log('setTimeout 宏任務(wù)隊(duì)列');
}, 0);
new Promise((resolve) => {
console.log('Promise 立即執(zhí)行');
resolve();
}).then(() => {
console.log('then 微任務(wù)隊(duì)列');
});
console.log('2');
//輸出順序
1
Promise 立即執(zhí)行
2
then 微任務(wù)隊(duì)列
setTimeout 宏任務(wù)隊(duì)列
解釋:
console.log('1');
setTimeout(() => {
console.log('setTimeout 宏任務(wù)隊(duì)列1');
new Promise((resolve) => {
console.log('Promise in setTimeout');
resolve();
}).then(() => {
console.log('then in setTimeout');
});
setTimeout(() => {
console.log('setTimeout 宏任務(wù)隊(duì)列2');
}, 0);
}, 0);
new Promise((resolve) => {
console.log('Promise 立即執(zhí)行1');
resolve();
}).then(() => {
console.log('then 微任務(wù)隊(duì)列1');
new Promise((resolve) => {
console.log('Promise 立即執(zhí)行2');
resolve();
}).then(() => {
console.log('then 微任務(wù)隊(duì)列2');
});
});
console.log('2');
//輸出順序
1
Promise 立即執(zhí)行1
2
then 微任務(wù)隊(duì)列1
Promise 立即執(zhí)行2
then 微任務(wù)隊(duì)列2
setTimeout 宏任務(wù)隊(duì)列1
Promise in setTimeout
then in setTimeout
setTimeout 宏任務(wù)隊(duì)列2
解釋:
const async1= async () => {
console.log('async1 1');
await async2();
console.log('async1 2');
}
const async2= async () => {
console.log('async2');
}
console.log('1');
setTimeout(() => {
console.log('setTimeout 宏任務(wù)隊(duì)列');
}, 0);
async1();
new Promise((resolve) => {
console.log('promise 立即執(zhí)行');
resolve();
}).then(() => {
console.log('then 微任務(wù)隊(duì)列');
});
console.log('2');
//輸出順序
1
async1 1
async2
promise 立即執(zhí)行
2
async1 2
then 微任務(wù)隊(duì)列
setTimeout 宏任務(wù)隊(duì)列
解釋:
Event Loop 是 JavaScript 異步編程的基石,它使得 JavaScript 能夠在不阻塞主線程的情況下處理各種異步操作。通過理解 Event Loop 的工作原理,我們可以更加高效地編寫異步代碼,避免潛在的錯(cuò)誤和性能問題。
*請認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。