Node內(nèi)部工作原理解析

Node is a runtime environment for executing JavaScript Code.
Node 既不是一種語言,也不是一個框架,而是一個能執(zhí)行 JavaScript 代碼的運行時環(huán)境。

最初的時候,Javascript 只能運行在瀏覽器中,靠的是 JS 引擎把 JavaScript 代碼轉(zhuǎn)成瀏覽器能識別的機器碼,并且不同的瀏覽器用的是不同的引擎,如 IE 使用 Charkra,F(xiàn)irefox 使用 SpiderMonkey,Chrome 使用 V8,因此,有時候 js 在不同的瀏覽器中運行會有不同的效果。

這些 Javascript 引擎都遵循 ECMAScript 標準,Javascript 則是 ECMAScript 的一個方言版本,能夠被web瀏覽器和許多其它的應(yīng)用支持。

瀏覽器就是一個能夠運行 Javascript 代碼的運行時環(huán)境,我們知道在 js 中,我們能夠訪問全局變量 window 、document,這些變量能夠讓我們與代碼所運行的環(huán)境進行交互。

之后,Node的創(chuàng)始人 Ryan Dahl 有了一個大膽的想法,想著如果 javascript 在瀏覽器以外的地方也能運行就好了,因此,他就把速度最快的 javascript 引擎——Google的 V8 引擎嵌入到了一個C++程序中,并且把這個程序叫做 Node。因此,跟瀏覽器類似,Node 也是一個 JavaScript 代碼的運行時環(huán)境,它包含了一個JS引擎,能夠執(zhí)行 JavaScript 代碼。但是,跟瀏覽器不同的是,它還提供了能夠支持其它功能的一些對象,沒有了能夠獲取 DOM 的 document 對象(document.getElementById()),但是能夠進行文件或者網(wǎng)絡(luò)操作(fs.createFile()、http.createServer() 等),這些操作就需要借助于其它的一些庫來實現(xiàn),如 libuv。

一、Node 目錄及架構(gòu)體系

node目錄

在 github 中,我們可以看到 node 庫的目錄,其中:

  • deps:包含了node所依賴的庫;
  • lib:包含了我們在項目中引入的用 javascript 定義的函數(shù)和模塊;
  • src:lib 庫對應(yīng)的C++實現(xiàn)。
Node architecture

在 Node 官方文檔的 Dependencies 中,我們也可以看到一些具體的所依賴的庫的作用。

  • v8 引擎的作用就是將 js 轉(zhuǎn)成 C++。
  • libuv 用于在C++中處理并發(fā)和進程構(gòu)建,具有跨平臺和異步能力。
  • c-ares:提供了異步處理 DNS 相關(guān)的能力。
  • http_parser、OpenSSL、zlib 等:提供包括 http 解析、SSL、數(shù)據(jù)壓縮等其他的能力。

接下來,我們就主要看兩個依賴庫:V8 和 Libuv。

V8

谷歌開源的 JavaScript 引擎,目的是使 JavaScript 能夠在瀏覽器之外的地方運行。前面說過,Javascript引擎是一個能夠?qū)?Javascript 語言轉(zhuǎn)換成瀏覽器能夠識別的低級語言或機器碼的程序。

Libuv

C++的開源項目,使 Node 能夠訪問操作系統(tǒng)的底層文件系統(tǒng)(file system),訪問網(wǎng)絡(luò)(networking)并且處理一些高并發(fā)相關(guān)的問題。

那么問題來了,既然 V8 能夠讓我們使用 JavaScript,libuv 給了我們一些操作系統(tǒng)、網(wǎng)絡(luò)等層面的訪問能力,我們還需要 Node 干嘛呢?

V8 大約70%由 C++ 實現(xiàn),30%由 JavaScript 實現(xiàn)。
Libuv 100% 由 C++ 實現(xiàn)。

原因不難理解:

(1)因為V8 和 libuv 都并非是用 JavaScript 寫的,對于我們前端兒來說,寫 C++ 是頭疼的事情,而 Node 為我們提供了一個很好的接口,用來將 javascript 應(yīng)用程序的 javascript 端與運行在我們計算機上的實際 c++ 關(guān)聯(lián)起來,從而實際地解釋和執(zhí)行 javascript 代碼。

(2)Node 封裝了一系列的 API 供我們使用,并且提供了一致的接口。

二、模塊實現(xiàn)

讓我們用實際的例子來說明一下 Node 到底是如何運行的:

  1. 選擇 Node standard libary 中的一個函數(shù);
  2. 在 node 源碼中找到它的實現(xiàn);
  3. 看下 V8 和 Libuv 是如何被用來實現(xiàn)函數(shù)功能的,即 node 是如何在 V8 和 Libuv 中利用和包裝功能的。

選擇一個函數(shù):scrypt.js

scrypt.js 是 Crypto 模塊中的一個函數(shù),Crypto 模塊通常用于對密碼進行hash化處理。

在源碼中,我們主要關(guān)注兩個文件夾:

  • Lib —— 包含了我們在項目中引入的所有函數(shù)和模塊的 JS 定義——JS side of node project。

  • Src —— 在這個文件夾里面是所有函數(shù)的 c++ 實現(xiàn),是 Node 實際導入 Libuv 和 V8 項目的地方,也是我們正在使用的所有函數(shù)和模塊實際實現(xiàn)的地方,比如FS模塊、Http模塊等等。

在 lib 文件夾下找到 scrypt 的函數(shù)實現(xiàn): node/lib/internal/crypto/scrypt.js

在這個 javascript 文件中,包含了對該函數(shù)的JS定義。這是 Node 標準庫中的函數(shù),就跟我們在任何 javascript 文件中編寫的函數(shù)一樣。

scrypt.js 文件中,你會發(fā)現(xiàn) internalBinding() 函數(shù),之前的版本是 Process.binding(),現(xiàn)在 node 團隊將其改為了 internalBinding(),因為它們現(xiàn)在無法從用戶空間訪問,而只能從 NativeModule.require() 獲得。

C++ binding Loaders

  1. process.binding(): 已成為歷史的 c++ 綁定加載程序,可以從用戶空間訪問,因為它被附加到了全局對象上。這些 c++ 綁定是通過 NODE_BUILTIN_MODULE_CONTEXT_AWARE() 創(chuàng)建的,并且它們的 nm_flags 設(shè)置為 NM_F_BUILTIN。我們無法確保這些綁定的穩(wěn)定性,因此需要時常處理它們引起的兼容性問題。
  2. process._linkedBinding(): 用于在其應(yīng)用程序中添加額外的c++綁定。這些c++綁定可以通過帶有 NM_F_LINKED 標志的 NODE_MODULE_CONTEXT_AWARE_CPP() 進行創(chuàng)建。
  3. internalBinding(): 私有的內(nèi)部c++綁定加載程序,用戶無法訪問,只能通過NativeModule.require() 獲得。這些c++綁定通過 NODE_MODULE_CONTEXT_AWARE_INTERNAL() 進行創(chuàng)建,并且它們的nm_flags 設(shè)置為 NM_F_INTERNAL。

內(nèi)部 JavaScript 模塊加載器:NativeModule

該模塊是用于加載 lib/**/*.jsdeps/**/*.js 中的JavaScript核心模塊的最小模塊系統(tǒng)。

所有核心模塊都通過由 js2c.py 生成的 node_javascript.cc 編譯成 Node 二進制文件,這樣可以更快地加載它們,而不需要I/O成本。

這個類使 lib/internal/*、deps/internal/* 模塊和 internalBinding() 在默認情況下對核心模塊可用,并且允許核心模塊通過 require('internal/bootstrap/loaders') 來引用自身,即使這個文件不是用 CommonJS 風格編寫的。

Process.binding / InternalBinding 實際上是C++函數(shù),是用于將Node標準庫中C++端和Javascript端連接起來的橋梁。

Process.binding() / internalBinding() 是如何工作的?

它們是 Node的 JS 端和 C++ 端之間的橋梁,也是 Node 為你實現(xiàn)大量內(nèi)部工作的地方。你的很多代碼最終都是依賴于c++代碼的。

現(xiàn)在,讓我們看一下在 src 文件夾中如何實現(xiàn) Node 的 c++ 端:node/src/node_crypto.cc。

node_crypto.cc —— crypto 模塊所依賴的并位于 Node 的 c++ 部分的實際代碼。

在該文件的最后,你會看到對 C++ setMethod() 的導出,這行代碼最終將由internalBinding() / process.binding() 進行調(diào)用。

SetMethod of C++ implementation for Scrypt.

這在某種程度上是將Node的Javascript端與Node的c++端連接起來。

這是我們所寫的函數(shù)實際實現(xiàn)的地方,100%純c++代碼。??
現(xiàn)在我想你應(yīng)該已經(jīng)了解了,當我們運行Javascript代碼,實際上它內(nèi)部依賴的是c++代碼。

現(xiàn)在,你可能對 V8 和 Libuv 是如何發(fā)揮作用的很好奇,那么,接下來,我們就一起來看一下。

在文件的頂部,我們可以看到有這么些代碼:

使用 v8

這里引入了 V8::Array 等類型,可以看到,在 node 源碼中使用 V8 的目的,本質(zhì)上是作為一個完整的中介,允許在 JavaScript 中所定義的值被轉(zhuǎn)換成等價的 C++ 值。

所有的 V8 語句都導入了 JS 概念的 C++ 定義。比如 C++ 對 JS 中的 False、Integer、null 或者 string 等的理解。

這就是實際的 V8 項目發(fā)揮作用的地方。V8 用于將我們在不同程序中的寫的 JS 類型的值,如 Boolean值、false值、null值、object值等轉(zhuǎn)換成C++中的值。

另一方面,Libuv 也在這里使用,但是不太容易被檢測到,我們可以搜索 uv,從而找到使用的地方。在 node_crypto.cc 的例子中,Libuv 用于 C++ 端的并發(fā)和進程處理。

看到這里,我想大家對 Node 的內(nèi)部工作原理應(yīng)該有一些了解了。

最后,總結(jié)成一張圖:

by Stephen Grider

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容