Node is a runtime environment for executing JavaScript Code.
Node 既不是一種語言羹奉,也不是一個(gè)框架汁针,而是一個(gè)能執(zhí)行 JavaScript 代碼的運(yùn)行時(shí)環(huán)境哟玷。
最初的時(shí)候粹胯,Javascript 只能運(yùn)行在瀏覽器中下隧,靠的是 JS 引擎把 JavaScript 代碼轉(zhuǎn)成瀏覽器能識別的機(jī)器碼收毫,并且不同的瀏覽器用的是不同的引擎攻走,如 IE 使用 Charkra,F(xiàn)irefox 使用 SpiderMonkey此再,Chrome 使用 V8昔搂,因此,有時(shí)候 js 在不同的瀏覽器中運(yùn)行會(huì)有不同的效果输拇。
這些 Javascript 引擎都遵循 ECMAScript 標(biāo)準(zhǔn)摘符,Javascript 則是 ECMAScript 的一個(gè)方言版本,能夠被web瀏覽器和許多其它的應(yīng)用支持。
瀏覽器就是一個(gè)能夠運(yùn)行 Javascript 代碼的運(yùn)行時(shí)環(huán)境逛裤,我們知道在 js 中瘩绒,我們能夠訪問全局變量 window
、document
别凹,這些變量能夠讓我們與代碼所運(yùn)行的環(huán)境進(jìn)行交互草讶。
之后,Node的創(chuàng)始人 Ryan Dahl 有了一個(gè)大膽的想法炉菲,想著如果 javascript 在瀏覽器以外的地方也能運(yùn)行就好了堕战,因此,他就把速度最快的 javascript 引擎——Google的 V8 引擎嵌入到了一個(gè)C++程序中拍霜,并且把這個(gè)程序叫做 Node嘱丢。因此,跟瀏覽器類似祠饺,Node 也是一個(gè) JavaScript 代碼的運(yùn)行時(shí)環(huán)境越驻,它包含了一個(gè)JS引擎,能夠執(zhí)行 JavaScript 代碼道偷。但是缀旁,跟瀏覽器不同的是,它還提供了能夠支持其它功能的一些對象勺鸦,沒有了能夠獲取 DOM 的 document
對象(document.getElementById()
)并巍,但是能夠進(jìn)行文件或者網(wǎng)絡(luò)操作(fs.createFile()
、http.createServer()
等)换途,這些操作就需要借助于其它的一些庫來實(shí)現(xiàn)懊渡,如 libuv。
一军拟、Node 目錄及架構(gòu)體系
在 github 中剃执,我們可以看到 node 庫的目錄,其中:
- deps:包含了node所依賴的庫懈息;
- lib:包含了我們在項(xiàng)目中引入的用 javascript 定義的函數(shù)和模塊肾档;
- src:lib 庫對應(yīng)的C++實(shí)現(xiàn)。
在 Node 官方文檔的 Dependencies 中漓拾,我們也可以看到一些具體的所依賴的庫的作用阁最。
- v8 引擎的作用就是將 js 轉(zhuǎn)成 C++。
- libuv 用于在C++中處理并發(fā)和進(jìn)程構(gòu)建骇两,具有跨平臺(tái)和異步能力速种。
- c-ares:提供了異步處理 DNS 相關(guān)的能力。
- http_parser低千、OpenSSL配阵、zlib 等:提供包括 http 解析馏颂、SSL、數(shù)據(jù)壓縮等其他的能力棋傍。
接下來救拉,我們就主要看兩個(gè)依賴庫:V8 和 Libuv。
V8
谷歌開源的 JavaScript 引擎瘫拣,目的是使 JavaScript 能夠在瀏覽器之外的地方運(yùn)行亿絮。前面說過,Javascript引擎是一個(gè)能夠?qū)?Javascript 語言轉(zhuǎn)換成瀏覽器能夠識別的低級語言或機(jī)器碼的程序麸拄。
Libuv
C++的開源項(xiàng)目派昧,使 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++ 實(shí)現(xiàn)主穗,30%由 JavaScript 實(shí)現(xiàn)泻拦。
Libuv 100% 由 C++ 實(shí)現(xiàn)。
原因不難理解:
(1)因?yàn)閂8 和 libuv 都并非是用 JavaScript 寫的忽媒,對于我們前端兒來說聪轿,寫 C++ 是頭疼的事情,而 Node 為我們提供了一個(gè)很好的接口猾浦,用來將 javascript 應(yīng)用程序的 javascript 端與運(yùn)行在我們計(jì)算機(jī)上的實(shí)際 c++ 關(guān)聯(lián)起來,從而實(shí)際地解釋和執(zhí)行 javascript 代碼灯抛。
(2)Node 封裝了一系列的 API 供我們使用金赦,并且提供了一致的接口。
二对嚼、模塊實(shí)現(xiàn)
讓我們用實(shí)際的例子來說明一下 Node 到底是如何運(yùn)行的:
- 選擇 Node standard libary 中的一個(gè)函數(shù)夹抗;
- 在 node 源碼中找到它的實(shí)現(xiàn);
- 看下 V8 和 Libuv 是如何被用來實(shí)現(xiàn)函數(shù)功能的纵竖,即 node 是如何在 V8 和 Libuv 中利用和包裝功能的漠烧。
選擇一個(gè)函數(shù):scrypt.js
scrypt.js 是 Crypto 模塊中的一個(gè)函數(shù),Crypto 模塊通常用于對密碼進(jìn)行hash化處理靡砌。
在源碼中已脓,我們主要關(guān)注兩個(gè)文件夾:
Lib —— 包含了我們在項(xiàng)目中引入的所有函數(shù)和模塊的 JS 定義——JS side of node project。
Src —— 在這個(gè)文件夾里面是所有函數(shù)的 c++ 實(shí)現(xiàn)通殃,是 Node 實(shí)際導(dǎo)入 Libuv 和 V8 項(xiàng)目的地方度液,也是我們正在使用的所有函數(shù)和模塊實(shí)際實(shí)現(xiàn)的地方,比如FS模塊、Http模塊等等堕担。
在 lib 文件夾下找到 scrypt 的函數(shù)實(shí)現(xiàn): node/lib/internal/crypto/scrypt.js
已慢。
在這個(gè) javascript 文件中,包含了對該函數(shù)的JS定義霹购。這是 Node 標(biāo)準(zhǔn)庫中的函數(shù)佑惠,就跟我們在任何 javascript 文件中編寫的函數(shù)一樣。
在
scrypt.js
文件中齐疙,你會(huì)發(fā)現(xiàn)internalBinding()
函數(shù)膜楷,之前的版本是Process.binding()
,現(xiàn)在 node 團(tuán)隊(duì)將其改為了internalBinding()
剂碴,因?yàn)樗鼈儸F(xiàn)在無法從用戶空間訪問把将,而只能從NativeModule.require()
獲得。
C++ binding Loaders
-
process.binding(): 已成為歷史的 c++ 綁定加載程序忆矛,可以從用戶空間訪問察蹲,因?yàn)樗桓郊拥搅巳謱ο笊稀_@些 c++ 綁定是通過
NODE_BUILTIN_MODULE_CONTEXT_AWARE()
創(chuàng)建的催训,并且它們的 nm_flags 設(shè)置為NM_F_BUILTIN
洽议。我們無法確保這些綁定的穩(wěn)定性,因此需要時(shí)常處理它們引起的兼容性問題漫拭。 -
process._linkedBinding(): 用于在其應(yīng)用程序中添加額外的c++綁定亚兄。這些c++綁定可以通過帶有
NM_F_LINKED
標(biāo)志的NODE_MODULE_CONTEXT_AWARE_CPP()
進(jìn)行創(chuàng)建。 -
internalBinding(): 私有的內(nèi)部c++綁定加載程序采驻,用戶無法訪問审胚,只能通過
NativeModule.require()
獲得。這些c++綁定通過NODE_MODULE_CONTEXT_AWARE_INTERNAL()
進(jìn)行創(chuàng)建礼旅,并且它們的nm_flags 設(shè)置為NM_F_INTERNAL
膳叨。
內(nèi)部 JavaScript 模塊加載器:NativeModule
該模塊是用于加載 lib/**/*.js
和 deps/**/*.js
中的JavaScript核心模塊的最小模塊系統(tǒng)。
所有核心模塊都通過由 js2c.py
生成的 node_javascript.cc
編譯成 Node 二進(jìn)制文件痘系,這樣可以更快地加載它們菲嘴,而不需要I/O成本。
這個(gè)類使 lib/internal/*
汰翠、deps/internal/*
模塊和 internalBinding()
在默認(rèn)情況下對核心模塊可用龄坪,并且允許核心模塊通過 require('internal/bootstrap/loaders')
來引用自身,即使這個(gè)文件不是用 CommonJS 風(fēng)格編寫的复唤。
Process.binding / InternalBinding 實(shí)際上是C++函數(shù)健田,是用于將Node標(biāo)準(zhǔn)庫中C++端和Javascript端連接起來的橋梁。
Process.binding() / internalBinding() 是如何工作的?
它們是 Node的 JS 端和 C++ 端之間的橋梁佛纫,也是 Node 為你實(shí)現(xiàn)大量內(nèi)部工作的地方抄课。你的很多代碼最終都是依賴于c++代碼的唱星。
現(xiàn)在,讓我們看一下在 src 文件夾中如何實(shí)現(xiàn) Node 的 c++ 端:node/src/node_crypto.cc
跟磨。
node_crypto.cc —— crypto 模塊所依賴的并位于 Node 的 c++ 部分的實(shí)際代碼间聊。
在該文件的最后,你會(huì)看到對 C++ setMethod()
的導(dǎo)出抵拘,這行代碼最終將由internalBinding() / process.binding()
進(jìn)行調(diào)用哎榴。
這在某種程度上是將Node的Javascript端與Node的c++端連接起來。
這是我們所寫的函數(shù)實(shí)際實(shí)現(xiàn)的地方僵蛛,100%純c++代碼尚蝌。??
現(xiàn)在我想你應(yīng)該已經(jīng)了解了,當(dāng)我們運(yùn)行Javascript代碼充尉,實(shí)際上它內(nèi)部依賴的是c++代碼飘言。
現(xiàn)在,你可能對 V8 和 Libuv 是如何發(fā)揮作用的很好奇驼侠,那么姿鸿,接下來,我們就一起來看一下倒源。
在文件的頂部苛预,我們可以看到有這么些代碼:
這里引入了 V8::Array
等類型,可以看到笋熬,在 node 源碼中使用 V8 的目的热某,本質(zhì)上是作為一個(gè)完整的中介,允許在 JavaScript 中所定義的值被轉(zhuǎn)換成等價(jià)的 C++ 值胳螟。
所有的 V8 語句都導(dǎo)入了 JS 概念的 C++ 定義昔馋。比如 C++ 對 JS 中的 False、Integer糖耸、null 或者 string 等的理解绒极。
這就是實(shí)際的 V8 項(xiàng)目發(fā)揮作用的地方。V8 用于將我們在不同程序中的寫的 JS 類型的值蔬捷,如 Boolean值、false值榔袋、null值周拐、object值等轉(zhuǎn)換成C++中的值。
另一方面凰兑,Libuv 也在這里使用妥粟,但是不太容易被檢測到,我們可以搜索 uv
吏够,從而找到使用的地方勾给。在 node_crypto.cc
的例子中滩报,Libuv 用于 C++ 端的并發(fā)和進(jìn)程處理。
看到這里播急,我想大家對 Node 的內(nèi)部工作原理應(yīng)該有一些了解了脓钾。
最后,總結(jié)成一張圖: