簡介
JavaScript 主要在 Node.js 和瀏覽器中的單一線程上執行(有一些例外,例如工作線程,這超出了目前文章的範圍)。在這篇文章中,我將嘗試解釋 Node.js 的並發機制,也就是事件循環。
在開始閱讀本文之前,您應該熟悉堆疊及其工作原理,我過去寫過這個想法,所以請查看堆疊和堆 - 不要在不了解它們的情況下開始編碼 - Moshe Binieli |中
簡介圖片
範例
我相信透過範例學習是最好的,因此我將從 4 個簡單的程式碼範例開始。我將分析範例,然後深入探討 Node.js 的架構。
範例1:
console.log(1);
console.log(2);
console.log(3);
// 輸出:
// 1
// 2
// 3
這個範例很簡單,第一步console.log(1)進入呼叫堆疊,被執行然後刪除,第二步console.log(2)進入呼叫堆疊,被執行然後刪除,以此類推console.log(3) 。
範例 1 的呼叫堆疊的視覺化
範例2:
console.log(1);
setTimeout(function foo(){
console.log(2);
}, 0);
console.log(3);
// 輸出:
// 1
// 3
// 2
在這個例子中我們可以看到我們立即運行setTimeout,所以我們期望console.log(2)在console.log(3)之前,但事實並非如此,讓我們了解其背後的機制。
基本事件循環架構(稍後我們將深入探討)
堆疊和堆:查看我關於此的文章(我在本文開頭添加了連結)
Web API:它們內建於您的 Web 瀏覽器中,能夠公開來自瀏覽器和周圍電腦環境的數據,並用它執行有用的複雜操作。它們不是 JavaScript 語言本身的一部分,而是建構在核心 JavaScript 語言之上,為您提供在 JavaScript 程式碼中使用的額外超能力。例如,Geolocation API 提供了一些簡單的 JavaScript 構造來檢索位置數據,因此您可以說,在 Google 地圖上繪製您的位置。在後台,瀏覽器實際上使用一些複雜的低階程式碼(例如C++)與裝置的GPS 硬體(或任何可用於確定位置資料的硬體)進行通信,檢索位置數據,並將其返回到瀏覽器環境以供使用在你的程式碼中。但同樣,這種複雜性已被 API 抽象化。
事件循環和回調隊列:完成Web API執行的函數將被移動到回調隊列,這是一個常規隊列資料結構,事件循環負責將下一個函數從回調隊列中出隊並將該函數發送到執行函數的調用堆疊。
執行順序
目前位於呼叫堆疊中的所有函數都會被執行,然後它們會從呼叫堆疊中彈出。
當呼叫堆疊為空時,所有排隊的任務都會一一彈出到呼叫堆疊中並執行,然後從呼叫堆疊中彈出。
讓我們來理解範例 2
console.log(1) 方法被呼叫並放置在呼叫堆疊上並被執行。
setTimeout 方法被調用並放置在調用堆疊上並被執行,此執行創建一個對setTimeout Web Api 的新調用,持續0 毫秒,當它完成時(立即,或者更準確地說,那麼它會最好說「盡快」)Web Api 將呼叫移至回呼佇列。
console.log(3) 方法被呼叫並放置在呼叫堆疊上並被執行。
Event Loop 看到 Call Stack 為空,從 Callback Queue 中取出「foo」方法放入 Call Stack,然後執行 console.log(2)。
範例 2 過程的視覺化
所以setTimeout(function,delay)中的delay參數並不代表函數執行後的精確時間延遲。它代表等待的最短時間,在該時間之後函數將在某個時間點執行。
範例3:
console.log(1);
setTimeout(function foo() {
console.log('foo');
}, 3500);
setTimeout(function boo() {
console.log('boo');
}, 1000);
console.log(2);
// 輸出:
// 1
// 2
// '噓'
// 'foo'
範例 3 過程的視覺化
範例 4:
console.log(1);
setTimeout(function foo() {
console.log('foo');
}, 6500);
setTimeout(function boo() {
console.log('boo');
}, 2500);
setTimeout(function baz() {
console.log(‘baz’);
}, 0);
for ([‘A’, ‘B’] 的 const 值) {
console.log(值);
}
函數二() {
console.log(2);
}
兩個();
// 輸出:
// 1
// 'A'
// 'B'
// 2
// '巴茲'
// '噓'
// 'foo'
範例 4 過程的視覺化
事件循環繼續執行任務佇列中等待的所有回調。在任務佇列中,任務大致分為兩類,即微任務和巨集任務。
宏任務(任務隊列)與微任務
更準確地說,實際上有兩種類型的隊列。
還有一些任務進入巨集任務佇列和微任務佇列,但我將介紹常見的任務。
常見的巨集任務有 setTimeout、setInterval 和 setImmediate。
常見的微任務有 process.nextTick 和 Promise 回呼。
執行順序
目前位於呼叫堆疊中的所有函數都會被執行,然後它們會從呼叫堆疊中彈出。
當呼叫堆疊為空時,所有排隊的微任務都會一一彈出到呼叫堆疊中並執行,然後從呼叫堆疊中彈出。
當呼叫堆疊和微任務佇列都為空時,所有排隊的巨集任務都會一一彈出到呼叫棧中並執行,然後從呼叫棧中彈出。
範例 5:
console.log(1);
setTimeout(function foo() {
console.log('foo');
}, 0);
Promise.resolve()
.then(函數 boo() {
console.log('boo');
});
console.log(2);
// 輸出:
// 1
// 2
// '噓'
// 'foo'
console.log(1) 方法被呼叫並放置在呼叫堆疊上並執行。
SetTimeout 正在執行,console.log('foo') 被移至 SetTimeout Web Api,0 毫秒後移至巨集任務佇列。
Promise.resolve() 正在被調用,正在被解析,然後 .then() 方法被移到微任務佇列。
console.log(2) 方法被呼叫並放置在呼叫堆疊上並執行。
Event Loop 發現呼叫堆疊為空,它首先從 Micro-Task 佇列中取出任務,即 Promise 任務,將 console.log('boo') 放在呼叫堆疊上並執行它。
事件循環看到呼叫堆疊為空,然後看到微任務為空,然後從巨集任務佇列中取出下一個任務,即 SetTimeout 任務,放入 console.log('foo')在呼叫堆疊上並執行它。
範例 5 過程的視覺化
對事件循環的高階理解
我正在考慮寫一篇關於事件循環機制如何運作的底層文章,它本身可以是一篇文章,所以我決定介紹該主題並附上深入解釋該主題的良好連結。
事件循環底層解釋
當 Node.js 啟動時,它會初始化事件循環,處理提供的輸入腳本(或放入 REPL),這可能會進行非同步 API 呼叫、調度計時器或呼叫 process.nextTick(),然後開始處理事件循環。
下圖顯示了事件循環操作順序的簡化概述。 (每個框將被稱為事件循環的一個“階段”,請查看介紹圖片以更好地理解循環。)
事件循環操作順序的簡化概述
每個階段都有一個要執行的回呼的 FIFO 佇列(我在這裡小心地說,因為根據實現的不同,可能會有其他資料結構)。雖然每個階段都有其特殊之處,但通常,當事件循環進入給定階段時,它將執行特定於該階段的任何操作,然後執行該階段佇列中的回調,直到佇列耗盡或達到最大回調數已執行。當佇列耗盡或達到回呼限制時,事件循環將進入下一階段,依此類推。
階段概述
計時器:此階段執行由 setTimeout() 和 setInterval() 安排的回呼。
掛起的回呼:執行延遲到下一個循環迭代的 I/O 回呼。
空閒,準備:僅內部使用。
Poll:檢索新的I/O事件;執行 I/O 相關回調(幾乎所有回調,關閉回呼、計時器調度的回調和 setImmediate() 除外);節點會在適當的時候阻塞在這裡。
檢查:這裡呼叫了 setImmediate() 回呼。
關閉回調:一些關閉回調,例如socket.on('關閉', ...).
前面的步驟如何適合這裡?
因此,前面僅使用“回調佇列”,然後使用“巨集和微佇列”的步驟是關於事件循環如何運作的抽象解釋。
還有另一件重要的事情要提,事件循環應該在處理巨集任務佇列中的一個巨集任務之後完全處理微任務佇列。
第 1 步:事件循環將循環時間更新為目前執行的當前時間。
步驟2:執行微隊列。
步驟 3:執行計時器階段的任務。
步驟4:檢查微隊列中是否有東西,如果有則執行整個微隊列。
步驟5:返回步驟3,直到計時器階段為空。
第 6 步:執行 Pending Callbacks 階段的任務。
步驟7:檢查微隊列中是否有東西,如果有則執行整個微隊列。
步驟 8:返回步驟 6,直到 Pending Callbacks 階段為空。
然後空閒…微隊列…輪詢…微隊列…檢查…微隊列…關閉回呼,然後重新開始。
因此,我很好地概述了事件循環在幕後如何實際工作,有很多遺漏的部分我在這裡沒有提到,因為實際文檔在解釋它方面做得很好,我將提供很好的鏈接文檔,我鼓勵您花10-20 分鐘來理解它們。
以上是事件循環的詳細內容。更多資訊請關注PHP中文網其他相關文章!