若是對前端瀏覽器的時間循環不太清楚,請看這篇文章。那麼node中的事件循環是什麼樣子呢?其實官方文檔有很清楚的解釋,本文先從node執行一個單文件提及,再講事件循環。html
任何高級語言的存在都有必定的執行環境,好比瀏覽器的代碼是在瀏覽器引擎中,那麼在node環境中也有必定的執行環境。咱們先來看一下官網的依賴包有哪些?前端
上面就是nodejs中依賴的模塊。那麼這些模塊之間是如何工做的呢?模塊之間的工做關係以下圖所示:node
主要過程以下:git
當即執行:能夠理解爲,須要v8引擎去處理的代碼;
異步執行:並非真正的異步,能夠理解爲,不須要v8引擎處理的和須要異步處理的。github
一個線程有惟一的一個事件循環(event loop)。線程非安全。
這裏須要理解兩點:web
這可能和咱們理解的不太同樣,Javascript代碼是單線程的,可是libuv不是單線程的,他能夠開啓多個線程,libuv 提供了一個調度的線程池,線程池中的線程數目,默認是4個,最多1024個(爲何?由於每個線程都會佔用資源,而內存是有限的),關於線程池的能夠看官方文檔。瀏覽器
對數據的操做無非就是讀和寫,線程安全,簡單來講,就是一個線程對這一份數據具備獨佔性,只有當該線程操做完成,其餘線程才能夠進行操做,固然線程安全的概念遠不止這些,詳細能夠看維基百科,這裏就簡單理解一下就好了。安全
事件循環圖,以下所示:網絡
主要分爲下面幾步:數據結構
step6: IO輪詢執行,直到超時,在阻塞執行以前,會計算超時時間,也就是中止輪詢的時間:
(用白話來理解,就是看有沒有要關閉的,有的話,就直接往下走,沒有的話,看看有哪一個事件比較急,到了點就去執行)
一般狀況下來說,文件的I/O會調用線程池,可是網絡請求的I/O老是用同一個線程。
node中全部的代碼幾乎都提供了同步(阻塞)和異步(非阻塞)的方式,你能夠選擇使用哪種方式,可是不要混合使用。
NodeJs中的定時器主要有三種:
三個定時器都有對應的取消函數:
setTimeout和setInterval行爲和在瀏覽器環境中的行爲相似,可是setTimeout和setImmediate有一點不一樣。在libuv中能夠看到,判斷循環是否結束的時候,是須要判斷是否還有待執行的函數,若是隻剩下一個setTimeout或者setInterval函數,那麼整個循環還會繼續存在,node提供了一個函數,可讓循環暫時休眠。
unref是可讓setTimeout暫時休眠,ref能夠再次喚醒
setImmediate是指定在事件循環結束執行的。主要發生在poll階段以後
若是poll隊列沒空,則一直執行,直到對列空位置若是poll隊列空了,有setImmediate事件,則會跳到check階段
若是poll隊列空了,沒有setImmediate事件,就會查看哪個timer事件快要到期了,轉到timers階段
依據上面的解釋,就有了setTimeout和setImmediate執行前後順序的問題:
setTimeout(() => { console.log('timeout'); }) setImmediate(() => { console.log('immediate); });
先說答案:
可能會有兩種狀況: timeout immediate 或者 immediate timeout
爲何?
主要是setTimeout在前或者後的問題,依賴於線程的執行速度。
主要是兩個階段:
二、v8引擎繼續執行,走到setImmediate
因此根本緣由是在於事件循環執行了一次仍是兩次。
那咱們接下來看看事件循環的邏輯
Node添加了這樣一個API,這個並不在事件循環的機制內,可是和時間循環機制相關。先來看一下定義:
nextTick的定義是在事件循環的下一個階段以前執行對應的回調。
雖然nextTick是這樣定義的,可是它並非爲了在事件循環的每一個階段去執行的。
主要有下面兩種應用場景:
let bar; function someAsyncApiCall(callback) { callback() process.nextTick(callback); } someAsyncApiCall(() => { console.log('bar', bar); // 1 }); bar = 1; // 輸出 undefined 1
輸出undefine的狀況是,由於執行函數的時候,bar並無被賦值,而process.nextTick則能保證整個執行環境都準備好了再去執行
const server = net.createServer(); server.on('connection', (conn) => { }); server.listen(8080); server.on('listening', () => { });
當v8引擎執行完代碼後,listen的回調會直接命中poll階段,那麼server的connect事件就不會執行
想要在構造函數中,去發送對應的事件,由於此時v8引擎尚未掃描到,而構造函數的代碼會當即執行,就須要nextTick
const EventEmitter = require('events'); const util = require('util'); function MyEmitter() { EventEmitter.call(this); // 這樣操做無效 this.emit('event'); // 應該這樣 // process.nextTick(() => { this.emit('event'); }); } util.inherits(MyEmitter, EventEmitter); const myEmitter = new MyEmitter(); myEmitter.on('event', () => { console.log('an event occurred!'); });
上面三個案例,重點在於v8引擎是單線程當即執行,而libuv則是異步執行,想要在異步循環以前執行一些操做就須要process.nextTick