在講 Event Loop (事件迴圈)之前,我們來了解點 node 的東西,來幫助我們更加明白事件迴圈是幹什麼的
Node 是什麼
Node.js 是一個基於 Chrome V8 引擎的 JavaScript 執行環境,Node 不是一門語言,是讓 js 執行在後端的,執行時不包括 js 全集,因為在服務端中不包含 DOM 和 BOM,Node 也提供了一些新的模組,比如 http,fs等模組。
Node 解決了什麼
Node 的首要目標是提供一種簡單的,用於建立高效能伺服器的開發工具 Web 伺服器的瓶頸在於併發的使用者量,對比 Java 和 Php 的實現方式
Node在處理高併發,I/O 密集場景有明顯的效能優勢
- 高併發,是指在同一時間併發訪問伺服器
- I/O 密集指的是檔案操作、網路操作、資料庫,相對的有 CPU 密集,CPU 密集指的是邏輯處理運算、壓縮、解壓、加密、解密
Web 主要場景就是接收客戶端的請求讀取靜態資源和渲染介面,所以 Node 非常適合 Web 應用的開發。
程式與執行緒
程式是作業系統分配資源和排程任務的基本單位,執行緒是建立在程式上的一次程式執行單位,一個程式上可以有多個執行緒。
- 瀏覽器執行緒
- 使用者介面-包括位址列、前進/後退按鈕、書籤選單等
- 瀏覽器引擎-在使用者介面和呈現引擎之間傳送指令(瀏覽器的主程式)
- 渲染引擎,也被稱為瀏覽器核心(瀏覽器渲染程式)
- 一個外掛對應一個程式(第三方外掛程式)
- GPU提高網頁瀏覽的體驗( GPU 程式)
- 瀏覽器渲染引擎
- 渲染引擎內部是多執行緒的,內部包含 ui 執行緒和 js 執行緒
- js 執行緒 ui 執行緒 這兩個執行緒互斥的,目的就是為了保證不產生衝突。
- ui 執行緒會把更改的放到佇列中,當 js 執行緒空閒下來的時候,ui 執行緒在繼續渲染
- js 單執行緒
- js 是單執行緒,為什麼呢?如果多個執行緒同時操作 DOM ,哪頁面不會很混亂?這裡所謂的單執行緒指的是主執行緒是單執行緒的,所以在 Node 中主執行緒依舊是單執行緒的。
- webworker 多執行緒
- 它和 js 主執行緒不是平級的,主執行緒可以控制 webworker,但是 webworker不能操作 DOM,不能獲取 document,window
- 其他執行緒
- 瀏覽器事件觸發執行緒(用來控制事件迴圈,存放 setTimeout、瀏覽器事件、ajax 的回撥函式)
- 定時觸發器執行緒(setTimeout 定時器所線上程)
- 非同步 HTTP 請求執行緒(ajax 請求執行緒)
單執行緒特點是節約了記憶體,並且不需要在切換執行上下文。而且單執行緒不需要管鎖的問題,所謂 鎖,在 java 裡才有鎖的概念,所以我們不用細研究
瀏覽器中的 Event Loop
- 同步任務都在主執行緒上執行,形成一個執行棧
- 主執行緒之外,還存在一個任務佇列。只要非同步任務有了執行結果,就在任務佇列之中放置一個事件。
- 一旦執行棧中的所有同步任務執行完畢,系統就會讀取任務佇列,將佇列中的事件放到執行棧中依次執行
- 主執行緒從任務佇列中讀取事件,這個過程是迴圈不斷的
整個的這種執行機制又稱為 Event Loop (事件迴圈)
node 中的 Event Loop
如圖(圖片是借鑑的):
* 我們寫的程式碼會交給 V8 引擎去進行處理 * 程式碼中可能會呼叫 nodeApi,node 會交給 LIBUV 庫處理 * LIBUV 通過阻塞 i/o 和多執行緒實現了非同步 io * 通過事件驅動的方式,將結果放到事件佇列中,最終交給我們的應用。同步,非同步 阻塞和非阻塞
- 阻塞和非阻塞指的是呼叫者的狀態,關注的是程式在等待呼叫結果時的狀態
- 同步和非同步指的是被呼叫者是如何通知的,關注的是訊息通知機制
巨集任務和微任務
- macro-task(巨集任務):
- setTimeout, setInterval, setImmediate, I/O
- micro-task(微任務):
- process.nextTick,
- 原生 Promise (有些實現的promise 將 then 方法放到了巨集任務中,瀏覽器預設放到了微任務),
- Object.observe (已廢棄),
- MutationObserver(不相容,已廢棄)
- MessageChannel(vue中 nextClick 實現原理)
同步程式碼先執行,執行是在棧中執行的,微任務大於巨集任務,微任務會先執行(棧),巨集任務後執行(佇列)
講到這裡,敲幾行程式碼來總結下我們上面講到的知識點把
《1》巨集任務,微任務在瀏覽器和 node 環境執行順序不同
// 這個列子裡面,包含了巨集任務,微任務,分別看看瀏覽器和node 列印的結果
console.log(1)
// 棧
setTimeout(function(){
console.log(2)
// 微任務
Promise.resolve(100).then(function(){
console.log('promise')
})
})
// 棧
let promise = new Promise(function(resolve, reject){
console.log(7)
resolve(100)
}).then(function(data){
// 微任務
console.log(data)
})
// 棧
setTimeout(function(){
console.log(3)
})
console.log(5)
// 瀏覽器結果:1 7 5 100 2 promise 3
// node 結果: 1 7 5 100 2 3 promise
複製程式碼
瀏覽器和 node 環境執行順序不同,瀏覽器是先把一個棧以及棧中的微任務走完,才會走下一個棧。node 環境裡面是把所以棧走完,才走微任務
《2》setTimeout setImmediate 都是巨集任務,誰優先執行呢?
setTimeout(function(){
console.log('timeout')
})
setImmediate(function(){
console.log('setImmediate')
})
// 結果列印:timeout setImmediate
複製程式碼
setTimeout setImmediate 這兩個取決於 node 的執行時間
《3》nextTick 和 then 都屬於微任務,誰優先執行呢?
process.nextTick(function(){
console.log('nextTick')
})
Promise.resolve().then(function(){
console.log('then')
})
// 結果列印:nextTick then
// 再加一個巨集任務呢
setImmediate(function(){
console.log('setImmediate')
})
// 結果列印:nextTick then setImmediate
複製程式碼
nextTick 會比 其他微任務、巨集任務執行快
《4》i/o 檔案操作(巨集任務),搭配微任務,誰優先執行呢?
let fs = require('fs');
fs.readFile('./1/log',function(){
console.log('fs')
})
process.nextTick(function(){
console.log('text')
})
// 結果列印:text fs
複製程式碼
i/o 檔案操作(巨集任務), 如果有微任務,先執行微任務,在執行檔案讀取