Node.js 原理簡介

Node.js 的官方文檔中有一段對 Node.js 的簡介,以下。html

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.node

大意就是說 Node.js 是基於 V8 的 JavaScript 運行時,事件驅動、非阻塞,所以輕量、高效。git

寥寥數語,並無說清楚 Node.js 究竟是什麼。參考了一些 Node.js 的官方文章以及社區裏的分析,整理以下。github

基礎架構

要想深刻理解 Node.js,咱們須要把 Node.js 進行必要的拆解,瞭解每一個組成部分的做用,它們之間如何交互,最終構成 Node.js 這個強大的運行時環境。npm

image

上圖是 Node.js 的內部結構圖。咱們能夠看到,自底向上主要能夠分紅三層:最底層是 Node.js 依賴的各類庫,有 V八、libuv 等;中間層是各類 Binding,也就是膠水代碼;最上層是應用代碼,可以使用 Node.js 的各類 API。promise

  • V8
    Google 開源的高性能 JavaScript 引擎,它將 JavaScript 代碼轉換成機器碼,而後執行,所以速度很是快。V8 以 C++ 語言開發,Google 的 Chrome 瀏覽器正是使用的 V8 引擎。瀏覽器

  • libuv
    libuv 以 C 語言開發,內部管理着一個線程池。在此基礎之上,提供事件循環(Event Loop)、異步網絡 I/O、文件系統 I/O等能力。網絡

  • 其餘底層依賴庫
    c-arescrypto (OpenSSL)http-parser 以及 zlib。這些依賴提供了對系統底層功能的訪問,包括網絡、壓縮、加密等。架構

Node.js 底層的依賴庫,有的以 C 語言開發,有的以 C++ 語言開發,如何讓應用代碼(JavaScript)可以與這些底層庫相互調用呢?這就須要中間層的 Binding 來完成。Binding 是一些膠水代碼,可以把不一樣語言綁定在一塊兒使其可以互相溝通。在 Node.js 中,binding 所作的就是把 Node.js 那些用 C/C++ 寫的庫接口暴露給 JS 環境。異步

中間層中,除了 Binding,還有 Addon。Binding 僅橋接 Node.js 核心庫的一些依賴,若是你想在應用程序中包含其餘第三方或者你本身的 C/C++ 庫的話,須要本身完成這部分膠水代碼。你寫的這部分膠水代碼就稱爲 Addon。本質上都是完成橋接的做用,使得應用與底層庫可以互通有無。

應用層的代碼,就沒必要多言了,咱們開發的應用、npm 安裝的包都運行在這裏。

事件循環 (event loop)

剛接觸 Node.js 的時候,就知道 Node.js 有一個事件循環,相似於 while(true),可是不知道每次循環何時開始,何時結束,在每次循環中,Node.js 是如何處理同步與異步代碼的。

要說事件循環,就不得不先說明一下 Node.js 的工做流程。下圖能夠簡要說明。

QRePV

一個 Node.js 應用啓動時,V8 引擎會執行你寫的應用代碼,保持一份觀察者(註冊在事件上的回調函數)列表。當事件發生時,它的回調函數會被加進一個事件隊列。只要這個隊列還有等待執行的回調函數,事件循環就會持續把回調函數從隊列中拿出並執行。

在回調函數執行過程當中,全部的 I/O 請求都會轉發給工做線程處理。libuv 維持着一個線程池,包含四個工做線程(默認值,可配置)。文件系統 I/O 請求和 DNS 相關請求都會放進這個線程池處理;其餘的請求,如網絡、平臺特性相關的請求會分發給相應的系統處理單元進行處理。

安排給線程池的這些 I/O 操做由 Node.js 的底層庫執行,完成以後觸發相應事件,對應的事件回調函數會被放入事件隊列,等待執行後續操做。這就是一個事件在 Node.js 中執行的整個生命週期。

前面說了,咱們只知道 Node.js 有事件循環,可是不知道每次循環什麼時候開始、什麼時候結束。下面就簡要說明一下每次循環的處理過程,詳細內容請參考Node.js 官方說明

一次事件循環,大概能夠分爲以下幾個階段:
image

圖中每個方塊,在事件循環中被稱爲一個階段(phase)。

每一個階段都有本身獨有的一個用於執行回調函數的 FIFO 隊列。當事件循環進入一個指定階段時,會執行隊列中的回調函數,當隊列中已經被清空或者執行的回調函數個數達到系統最大限制時,事件循環會進入下一個階段。

上圖中總共有6個階段:

  • timers: 該階段執行由 setTimeout()setInterval() 設置的回調函數。
  • I/O callbacks: 執行除了close 回調、timers 以及
    setImmediate() 設置的回調之外的幾乎全部的回調。
  • idle,prepare: 僅供內部使用。
  • poll: 檢索新的 I/O 事件;在適當的時候 Node.js 會阻塞等待。
  • check: 執行 setImmediate() 設置的回調。
  • close callbacks: 執行關閉回調。好比: socket.on('close', ...).

這裏有個使人困惑的地方,I/O callbackspoll 這兩個階段有什麼區別? 既然 I/O callbacks 中已經把回調都執行完了,還要 poll 作什麼?

查閱了libuv 的文檔後發現,在 libuv 的 event loop 中,I/O callbacks 階段會執行 Pending callbacks。絕大多數狀況下,在 poll 階段,全部的 I/O 回調都已經被執行。可是,在某些狀況下,有一些回調會被延遲到下一次循環執行。也就是說,在 I/O callbacks 階段執行的回調函數,是上一次事件循環中被延遲執行的回調函數。

還須要提到的一點是 process.nextTick()process.nextTick() 產生的回調函數保存在一個叫作 nextTickQueue 的隊列中,不在上面任何一個階段的隊列裏面。噹噹前操做完成後,nextTickQueue 中的回調函數會當即被執行,無論事件循環處在哪一個階段。也就是說,在 nextTickQueue 中的回調函數被執行完畢以前,事件循環不會往前推動。

測試與實踐

以下代碼中使用了 setTimeout(), setInterval(), setImmediate(), promise, process.nextTick(),可藉助於輸出結果,理解事件循環。

'use strict';

const fs = require('fs');

console.log('script start');

const interval = setInterval(() => {  
  console.log('setInterval')
}, 500);

setTimeout(() => {  
  console.log('setTimeout 1');
  Promise.resolve().then(() => {
    console.log('promise 3');
  }).then(() => {
    console.log('promise 4');
    process.nextTick(() => {
      console.log('nextTick 1');
    });
  }).then(() => {
    setTimeout(() => {
      console.log('setTimeout 2');
      Promise.resolve().then(() => {
        console.log('promise 5');
      }).then(() => {
        console.log('promise 6');
        process.nextTick(() => {
          console.log('nextTick 2');
        });
      }).then(() => {
        clearInterval(interval);
      });
    }, 0);
  });
}, 1000);

Promise.resolve().then(() => {  
  console.log('promise 1');
}).then(() => {
  console.log('promise 2');
});

setImmediate(() => {
  console.log('setImmediate 1');
});

console.log('script done');

執行結果爲:

script start
script done
promise 1
promise 2
setImmediate 1
setInterval
setTimeout 1
promise 3
promise 4
nextTick 1
setInterval
setTimeout 2
promise 5
promise 6
nextTick 2
相關文章
相關標籤/搜索