其實在前面的文章我也講述過瀏覽器中的eventloop。然而在NodeJs中的eventloop與瀏覽器的是有區別的。對於寫的人來說掌握eventloop是一項很重要的技能。因為這意味著你不僅是會寫js,而對NodeJs也是有研究的。
我們知道NodeJs的本質是把瀏覽器的v8搬到了作業系統中執行,因此也把瀏覽器的事件迴圈拿過來了。可是為什麼會出現eventloop這樣的設計呢?
從歷史原因上來看,js在設計時只是一門很簡單的為了在頁面上操作一下dom的語言(相信大家都聽過js只用了10天就設計出來的故事)。出於這個目標,我們當然希望js的執行儘可能的簡單,輕量,有多輕呢?輕到js的渲染引擎是在一個執行緒中執行的。
那麼問題來了如果是在一個執行緒上執行js,當程式碼是線性的時候,當然是沒有問題的。但在頁面上,我們需要使用者的互動,而這些互動是不知道為什麼時候發生的。那js要怎麼處理?如果眼前有正在執行的程式碼,一個使用者互動進來之後,程式該怎麼反應?如果先處理使用者的互動,那原來的程式就會被暫停(也就是阻塞)。為了避免這種阻塞,js採用了一個辦法,就是用一個訊息佇列,來存放這種使用者互動。等所有的程式跑完之後,再去訊息佇列中拿互動事件,然後執行。這樣就解決了阻塞的問題了。
我們都知道瀏覽器在瀏覽頁面的時候,使用者互動是隨時可能發生的,為了可以即時響應使用者。js是不會關閉的,他會不停的迴圈。大致如下:
向訊息佇列拿任務-->執行任務-->執行完畢--> 向訊息佇列拿任務--> ....
當然我們在之前的事件迴圈文章中講過,為了給不同的非同步任務分類,在事件迴圈中其實是有宏任務和微任務的區分的。他們的執行大致為
向訊息佇列拿微任務-->執行微任務-->微任務執行完畢--> 向訊息佇列拿宏任務-->執行宏任務-->宏任務執行完畢-->向訊息佇列拿微任務-->...
的事件迴圈其實大致思路跟在瀏覽器上的是相似的,但nodeJs對不同的宏任務又作出了不同時期的區分。下面是官方的流程圖:
可以看到nodeJs中每次事件迴圈被分成了具體的6個時期,每個時期會用指定的宏任務。然後在每個時期的宏任務執行之前,會優先執行完微任務佇列。
timers | 執行由setTimeout() 和 setInterval() 觸發的回撥 |
---|---|
pending callbacks | 執行延遲到下一個迴圈迭代的I / O回撥 |
idle, prepare | 只在內部使用,開發者可以不關注 |
poll | 檢索新的I / O事件;執行I / O相關的回撥(會執行幾乎所有的回撥,除了 close callbacks 以及 timers 排程的回撥和 setImmediate() 排程的回撥,在恰當的時機將會阻塞在此階段) |
check | 執行setImmediate() |
close callbacks | 比如socket.on('close', ...) |
其實通過上述表格,我們已經很清晰知道整個事件迴圈機制的執行順序了。但可能大家還會有一些疑問。下面來詳細講一下。
這個階段其實是處理由於作業系統出錯,導致一些本應在上次事件迴圈中執行的回撥。例如一些TCP錯誤。因此這部分,開發者不能主動操作,是NodeJs的一些容錯機制。
同樣的,setImmediate是nodejs特有的api,他可以立即建立一個非同步宏任務。不僅如此,nodejs在事件迴圈中還專門設了一個check時期,在這個時期會專門執行setImmediate的回撥。甚至你可以在這個時期中如果不停的產生setImmediate回撥,eventloop會優先處理。
這個時期處理關閉事件,如socket.on('close', ...) 等這樣可以確保在一些通訊結束前,所有任務都完成了。
我們先來回顧瀏覽器與nodejs的差異:
任務 | 瀏覽器 | Node |
---|---|---|
I/O | ✅ | ✅ |
setTimeout | ✅ | ✅ |
setInterval | ✅ | ✅ |
setImmediate | ❌ | ✅ |
requestAnimationFrame | ✅ | ❌ |
任務 | 瀏覽器 | Node |
---|---|---|
process.nextTick | ❌ | ✅ |
MutationObserver | ✅ | ❌ |
Promise.then catch finally | ✅ | ✅ |
可以看到process.nextTick是nodejs特有的微任務,不僅如此,process.nextTick()的優先順序高於所有的微任務,每一次清空微任務列表的時候,都是先執行 process.nextTick()
不僅是任務型別上有差異,在執行上2個環境其實也有差異。在瀏覽器上執行任務的時候,每執行一個宏任務之前,需要先確保微任務佇列執行完了。而在nodejs上是每個時期之前,先確保微任務佇列執行完。也就是說在假如在timer時期,會先把所有setTimeout,setInterval的宏任務執行完。在執行完微任務,再進入下個時期。
注意:以上執行規則是在nodejs的v11版本之前的規則。在11版本之後nodejs的執行輸出是跟瀏覽器一樣的。
setImmediate() 和 setTimeout()的執行先後順序是不一定的,就是說如果你不停地執行以下程式碼,每次得到的結果可能是不一樣的。
setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immediate'); });
其中的原因是程式對時間的處理是有誤差的。在setTimeout方法中設定的時間,不一定是準確的。同時在回撥觸發時,也無法確認事件迴圈處在哪個時期,可能是timer,也可能是check。所有會有不同的結果。
eventloop是js執行機制裡的重點內容,對於NodeJs來說,eventloop的操作空間則更大。因為它被細分為不同的時期,從而讓我們可能把邏輯進一步細化。同時利用nextTick的最高優先順序,可以寫出在瀏覽器無法實現的程式碼。因此對於深入NodeJs的開發者來說,eventloop往往是他們考察新人對NodeJs理解的第一步。
更多node相關知識,請存取:!!
以上就是一文帶你瞭解Node.js中的eventloop的詳細內容,更多請關注TW511.COM其它相關文章!