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

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 中瘩绒,我們能夠訪問全局變量 windowdocument别凹,這些變量能夠讓我們與代碼所運(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)體系

node目錄

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

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

在 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)行的:

  1. 選擇 Node standard libary 中的一個(gè)函數(shù)夹抗;
  2. 在 node 源碼中找到它的實(shí)現(xiàn);
  3. 看下 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

  1. process.binding(): 已成為歷史的 c++ 綁定加載程序忆矛,可以從用戶空間訪問察蹲,因?yàn)樗桓郊拥搅巳謱ο笊稀_@些 c++ 綁定是通過 NODE_BUILTIN_MODULE_CONTEXT_AWARE() 創(chuàng)建的催训,并且它們的 nm_flags 設(shè)置為 NM_F_BUILTIN洽议。我們無法確保這些綁定的穩(wěn)定性,因此需要時(shí)常處理它們引起的兼容性問題漫拭。
  2. process._linkedBinding(): 用于在其應(yīng)用程序中添加額外的c++綁定亚兄。這些c++綁定可以通過帶有 NM_F_LINKED 標(biāo)志的 NODE_MODULE_CONTEXT_AWARE_CPP() 進(jìn)行創(chuàng)建。
  3. 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/**/*.jsdeps/**/*.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)用哎榴。

SetMethod of C++ implementation for Scrypt.

這在某種程度上是將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

這里引入了 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é)成一張圖:

by Stephen Grider

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桩警,一起剝皮案震驚了整個(gè)濱河市可训,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捶枢,老刑警劉巖握截,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異烂叔,居然都是意外死亡谨胞,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門蒜鸡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胯努,“玉大人,你說我怎么就攤上這事术瓮】的簦” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵胞四,是天一觀的道長恬汁。 經(jīng)常有香客問我,道長辜伟,這世上最難降的妖魔是什么氓侧? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮导狡,結(jié)果婚禮上约巷,老公的妹妹穿的比我還像新娘。我一直安慰自己旱捧,他們只是感情好独郎,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著枚赡,像睡著了一般氓癌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贫橙,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天贪婉,我揣著相機(jī)與錄音,去河邊找鬼卢肃。 笑死疲迂,一個(gè)胖子當(dāng)著我的面吹牛才顿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尤蒿,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼郑气,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了优质?” 一聲冷哼從身側(cè)響起竣贪,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎巩螃,沒想到半個(gè)月后演怎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡避乏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年爷耀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拍皮。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡歹叮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出铆帽,到底是詐尸還是另有隱情咆耿,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布爹橱,位于F島的核電站萨螺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏愧驱。R本人自食惡果不足惜慰技,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望组砚。 院中可真熱鬧吻商,春花似錦、人聲如沸糟红。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盆偿。三九已至柒爸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陈肛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工兄裂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留句旱,地道東北人阳藻。 一個(gè)月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像谈撒,于是被迫代替她去往敵國和親腥泥。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容