最近在學(xué)習(xí) Node,看的是樸靈老師編著的《深入淺出Node.js》捕儒。這本書和我看過的其他技術(shù)類書籍有些不同冰啃,書中不僅講解了 Node 的歷史,發(fā)展,特點(diǎn)亿笤,應(yīng)用場(chǎng)景等等翎迁。還說明了很多原理,讓我有一種“我靠~原來是這樣實(shí)現(xiàn)的熬谎Α汪榔!”,恍然大悟的感覺肃拜。作為一名前端程序猿痴腌,也經(jīng)常使用比如 webpack、gulp燃领、vue-cli ...這些自動(dòng)化的前端工具士聪,他們的開發(fā)或環(huán)境也是基于 node啊,通過這本書理解了很多之前并不是很理解的問題猛蔽。Node 的橫空出世也將前端推上了巔峰剥悟,感謝樸靈老師和其他本書的支持者,能讓我更好的學(xué)習(xí) Node曼库!
因?yàn)槲冶旧硎且幻岸碎_發(fā)人員区岗,所以對(duì) Node 學(xué)習(xí)和理解,基本是從前端角度去理解的毁枯,如果有錯(cuò)誤或誤解的地方慈缔,歡迎各位及時(shí)指出更正。
Node 給 JavaScript 帶來的意義
我們知道瀏覽器中除了 V8 作為 JavaScript 的引擎外种玛,還有一個(gè) WebKit 布局引擎(這個(gè)還不太清楚)藐鹤,HTML5的發(fā)展中,定義了更多更豐富的 API 赂韵,瀏覽器提供了越來越多的功能暴露給 JavaScript 和 HTML 標(biāo)簽娱节。但長久以來,JavaScript 被限制在瀏覽器的沙箱中運(yùn)行右锨,它的能力取決于瀏覽器的中間層提供的支持有多少括堤。也就是一直受瀏覽器限制,做不了啥“大事”绍移,不能訪問本地文件悄窃,不能操作數(shù)據(jù)庫。
但是在 Node 中蹂窖,JavaScript 可以隨心所欲的訪問本地文件轧抗,搭建服務(wù)器端,連接數(shù)據(jù)庫瞬测。而且 Node 的語法和 JavaScript 的語法是一樣的横媚,也就是說 JS 代碼纠炮,可以實(shí)現(xiàn)后端的一些功能,這就牛X了有沒有灯蝴。
Node 的特點(diǎn)
Node 的特點(diǎn)有三個(gè):
- 異步 I/O
- 事件與回調(diào)函數(shù)
- 單線程
其實(shí)了解了這三個(gè)特點(diǎn)后恢口,你會(huì)覺得這三個(gè)差不多是一個(gè)特點(diǎn),只是不同階段的不同特征穷躁。
異步 I/O
作為前端工程師耕肩,對(duì)于異步的理解可以說的小菜一碟了,例如我們和 Ajax 基本是抬頭不見低頭見了:
$.post('url',{title:'深入淺出Node.js'},function(data){
console.log(111);
});
console.log(222);
前端開發(fā)的肯定知道问潭,“111”是在“222”之后輸出的猿诸。在 Ajax 執(zhí)行以后,是直接向下執(zhí)行“222”的狡忙,并不會(huì)等待 Ajax 執(zhí)行完梳虽,只知道“111”會(huì)在請(qǐng)求成功之后才執(zhí)行,但具體時(shí)間未知灾茁。這就是一個(gè)典型的異步 I/O 操作窜觉。
在 Node 中異步調(diào)用和 Ajax 基本差不多,以讀取文件為例:
var fs = require('fs');
fs.readFile('path',function(err,file){
console.log(111);
});
console.log(222);
這里的“111”也是在“222”之后輸出的删顶。
在 Node 中絕大多數(shù)的操作都是異步調(diào)用的竖螃,這樣極大的提升了效率淑廊。
事件與回調(diào)函數(shù)
前端開發(fā)中逗余,回調(diào)函數(shù)無處不在,Node 中也是如此季惩。從上面的例子可以看出录粱,Ajax 中 success 就是回調(diào)。除了異步和事件外画拾,回調(diào)也是 Node 的一大特色啥繁。
單線程
Node 保留了 JavaScript 在瀏覽器中單線程的特點(diǎn)。單線程的最大好處是不用擔(dān)心多線程中的狀態(tài)同步的問題青抛,Node 中沒有死鎖的存在旗闽,也沒有線程上下文交換帶來性能上的開銷。
但是單線程也有缺點(diǎn):
- 無法利用多核 CPU
- 錯(cuò)誤會(huì)引起整個(gè)應(yīng)用崩潰蜜另,健壯性不足
- 大量計(jì)算占用 CPU 無法繼續(xù)調(diào)用異步 I/O
后面适室,書中也講解了如何避免這些弱點(diǎn),更高效的使用 Node.js举瑰。
Node 應(yīng)用場(chǎng)景
說到應(yīng)用只有兩種結(jié)果捣辆,適合與不適合。探討較多的就是 I/O 密集型和 CPU 密集型此迅。
I/O 密集型自然不用多說汽畴,Node 對(duì) I/O 的處理能力還是很6的。至于計(jì)算方面的 CPU 密集型,有兩種方法:
- 通過編寫 C/C++ 擴(kuò)展模塊袁梗,充分利用 CPU
- 通過子進(jìn)程方式分擔(dān)計(jì)算任務(wù)
這里有一句精華:CPU 密集不可怕轰异,如何合理調(diào)度是訣竅!
上面的內(nèi)容是從大體上了解 Node罢坝,它的特點(diǎn)和應(yīng)用場(chǎng)景绳匀。下面將會(huì)從各個(gè)相關(guān)模塊,來深入認(rèn)識(shí) Node炸客,以及各方面的實(shí)現(xiàn)原理疾棵。
模塊機(jī)制
CommonJS 規(guī)范
CommonJS 規(guī)范為 JavaScript 制定了一個(gè)美好的愿景——希望 JavaScript 能在任何地方運(yùn)行。
CommonJS 規(guī)范的提出痹仙,主要是為了彌補(bǔ)后端 JavaScript 沒有標(biāo)準(zhǔn)的缺陷是尔,已達(dá)到像 Python,Ruby 和 Java 具備開發(fā)大型應(yīng)用的基礎(chǔ)能力开仰,而不是停留在小腳本程序的階段拟枚。讓它不僅可以開發(fā)客戶端應(yīng)用,還可以開發(fā):
- 服務(wù)器端 JavaScript 應(yīng)用程序
- 命令行工具
- 桌面圖形應(yīng)用程序
- 混合應(yīng)用
CommonJS模塊規(guī)范
主要分為:模塊引用,模塊定義谓娃,模塊標(biāo)識(shí) 3個(gè)部分脚乡。
require('moduleName')
引入模塊。
exports.moduleName
exports 對(duì)象用于到處當(dāng)前模塊的方法或變量滨达,是唯一的導(dǎo)出出口奶稠。
模塊標(biāo)識(shí)就是傳遞給 require()
方法的參數(shù),它必須是小駝峰命名的字符串捡遍,或者相對(duì)路徑锌订,或絕對(duì)路徑,可以省略 .js 后綴画株。
每個(gè)模塊具有獨(dú)立的空間辆飘,塊之間互不干擾。
require 的尋找機(jī)制谓传,是沿路徑向上逐級(jí)遞歸蜈项,直到根目錄下的 node_modules 目錄。
文件擴(kuò)展名分析
require 在分析標(biāo)識(shí)符的過程中良拼,如果沒寫上文件擴(kuò)展名战得,Node 會(huì)按 .js .node .json 的順序依次嘗試。
在嘗試的過程中庸推,需要調(diào)用 fs 模塊同步阻塞地判斷文件存不存在常侦,這樣會(huì)引起性能問題浇冰。小訣竅就是:如果是 .node 或 .json 結(jié)尾的文件,引入時(shí)名稱就不要省略后綴了聋亡,加上就 OK 啦肘习。
目錄分析和包
如果 require()
引入時(shí),寫的不是文件名坡倔,是目錄名漂佩,此時(shí) Node 會(huì)把目錄當(dāng)做是一個(gè)包來處理,然后在當(dāng)前目錄下查找 package.json 文件罪塔,通過JSON.parse()
解析出包描述對(duì)象投蝉,從 main 屬性指定的文件名進(jìn)行定位。
如果連 package.json 文件都沒有征堪,Node 會(huì)將文件名默認(rèn)為 index 瘩缆,依次查找 index.js ,index.node 佃蚜,index.json 文件庸娱。
如果找了一圈這些也沒找著,那 Node 可就不樂意了谐算,就該給你扔個(gè)錯(cuò)誤出來了(not found module...)熟尉。
JavaScript模塊的編譯
有幾個(gè)疑惑點(diǎn)斤儿,每個(gè)模塊里都存在 require,exports腮考,module 這3個(gè)變量雇毫,但是在模塊中并沒有定義,他們從何而來踩蔚?
還有在 Node 的 API 文檔中,每個(gè)模塊還有 __filename枚粘,__dirname 這兩個(gè)變量馅闽,他們又從哪來的?
原因是馍迄,在編譯過程中福也,Node 對(duì)獲取的 JavaScript 文件內(nèi)容進(jìn)行了頭尾包裝。在頭部添加了“(function(exports,require,module,__filename,__dirname){\n”攀圈,在尾部添加了“\n})”暴凑。
(function(exports,require,module,__filename,__dirname){
var math = require('math');
exports.area = function(radius){
return Math.PI * radius * radius;
};
});
也就是 Node 把引入的 js 文件內(nèi)容包在了一個(gè)函數(shù)里,這樣就形成了一個(gè)封閉作用域赘来,不會(huì)對(duì)其他模塊造成污染现喳,這就是 Node 對(duì) CommonJS 規(guī)范的實(shí)現(xiàn)凯傲。
還有就是為何存在 exports,也存在 module.exports嗦篱,給 exports 賦值卻失敗的情況冰单。
exports = function(){
// 這樣是不行的
};
原因在于,exports 對(duì)象是通過形參方式傳入的灸促,直接賦值會(huì)改變形參的作用诫欠,但并不能改變作用域外的值。
var change = function(a){
// 直接使用exports.a更改相當(dāng)于在這里改a的值浴栽,改不了外面的a
a = 100;
console.log(a); // 100
};
// 使用module.export.a 就可以改這個(gè)a的值
var a = 10;
change(a);
console.log(a); // 10
核心模塊
Node 自帶的模塊就是核心模塊荒叼。
核心模塊分為 C/C++ 編寫 和 JavaScript 編寫 兩部分。由 C/C++ 編寫的模塊也叫做內(nèi)建模塊典鸡。
在編譯 C/C++ 文件之前甩挫,會(huì)把 JavaScript 模塊文件編譯成 C/C++ 代碼,將所有內(nèi)置的 JavaScript 代碼轉(zhuǎn)換成 C++ 里的數(shù)組椿每,JavaScript 代碼以字符串形式存儲(chǔ)在 node 命名空間中伊者。(此時(shí)是不可執(zhí)行的)
Node 執(zhí)行時(shí),JavaScript 核心模塊间护,經(jīng)過 頭尾包裝 后導(dǎo)出亦渗,得以引入。(此時(shí)是可執(zhí)行的)
內(nèi)建模塊會(huì)編譯成二進(jìn)制文件汁尺,一旦 Node 執(zhí)行法精,它們會(huì)直接加載進(jìn)內(nèi)存當(dāng)中,直接就可執(zhí)行痴突。
在 Node 所有的模塊類型中搂蜓,存在著一種依賴關(guān)系,即文件模塊可能依賴核心模塊辽装,核心模塊可能依賴內(nèi)建模塊帮碰。 (文件模塊即第三方自己下載的模塊)
通常,文件模塊不推薦直接調(diào)用內(nèi)建模塊拾积。如需調(diào)用殉挽,直接調(diào)用核心模塊即可。
Node 在啟動(dòng)時(shí)拓巧,會(huì)生成一個(gè)全局變量 process斯碌,并提供 Binding() 方法來協(xié)助加載來加載內(nèi)建模塊。所以核心模塊中基本都封裝了內(nèi)建模塊肛度。
核心模塊引入流程
為了符合 CommonJS 模塊規(guī)范傻唾,從 JavaScript 到 C/C++ 的過程是很復(fù)雜的,它要經(jīng)歷 C/C++ 層面的內(nèi)建模塊定義承耿、(JavaScript)核心模塊的定義和引入冠骄、(JavaScript)文件模塊層面的引入伪煤。
但是使用 require()
就十分簡(jiǎn)潔、友好了猴抹。
編寫核心模塊
本人功力還不到火候带族,這一節(jié)就直接跳過了,有能力的朋友可自行研究啦蟀给。
模塊調(diào)用棧
C/C++ 內(nèi)建模塊屬于最底層模塊蝙砌,它屬于核心模塊,主要提供 API 給 JavaScript 核心模塊和第三方的文件模塊調(diào)用跋理。
JavaScript 核心模塊主要有兩個(gè)職責(zé):
- 作為C/C++ 內(nèi)建模塊的封裝層和橋接層择克,供文件模塊調(diào)用
- 純粹的功能模塊
文件模塊就是第三方模塊,自己編寫的模塊前普,不要被它搞暈呦肚邢。
包與NPM
Node 有自帶的模塊,但是開發(fā)中肯定不夠用笆们洹骡湖!所以有世界各地大牛們自己開發(fā)了很多的包,但是第三方的包是分散在世界各地的峻厚,相互不能引用响蕴。這時(shí)候,包和NPM就發(fā)揮作用了惠桃,它就是將模塊聯(lián)系起來的一種機(jī)制浦夷。
CommonJS 的包規(guī)范有兩個(gè)部分:
-
包結(jié)構(gòu):組織包中的各種文件
- 包描述文件:包的相關(guān)信息(就是那個(gè)package.json文件)
NPM 常用功能
我經(jīng)常使用的就是npm install 包名稱 -S
下載一些包,npm run dev
執(zhí)行一些命令辜王,具體其他更多功能可以百度一下啦(太懶了太懶了)劈狐。
Node 通過 CommonJS 規(guī)范,組織了自身的原生模塊呐馆,彌補(bǔ) JavaScript 弱結(jié)構(gòu)問題肥缔,形成了穩(wěn)定的結(jié)構(gòu),并向外提供服務(wù)摹恰。
NPM 組織了第三方模塊辫继,使項(xiàng)目開發(fā)中的依賴問題得到解決,借助第三方開源力量俗慈,也發(fā)展了自己,形成一個(gè)良性的生態(tài)系統(tǒng)遣耍。
未完待續(xù)闺阱。。舵变。酣溃。瘦穆。。
文章只是本人學(xué)習(xí) Node 過程中赊豌,按自己的理解總結(jié)的一些筆記扛或,若有錯(cuò)誤之處,歡迎各位及時(shí)指出碘饼,一起探討更好的答案熙兔。
公眾號(hào):前端很忙
做一個(gè)喜歡分享的前端開發(fā)者!
獲取更多干貨分享艾恼,歡迎來搞住涉!