前端修煉——Node.js(一)

最近在學(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ù)庫。


Chrome瀏覽器和Node的組件構(gòu)成

但是在 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 操作窜觉。

經(jīng)典Ajax調(diào)用

在 Node 中異步調(diào)用和 Ajax 基本差不多,以讀取文件為例:

var fs = require('fs');
fs.readFile('path',function(err,file){
    console.log(111);
});
console.log(222);

這里的“111”也是在“222”之后輸出的删顶。

經(jīng)典Node異步調(diào)用

在 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)原理疾棵。

是時(shí)候展現(xiàn)真正的技術(shù)了

模塊機(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)用
Node與瀏覽器及W3C組織众弓、CommonJS組織恩溅、ECMAScript之間的關(guān)系
CommonJS模塊規(guī)范

主要分為:模塊引用模塊定義谓娃,模塊標(biāo)識(shí) 3個(gè)部分脚乡。
require('moduleName')引入模塊。
exports.moduleNameexports 對(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.jsindex.node 佃蚜,index.json 文件庸娱。

如果找了一圈這些也沒找著,那 Node 可就不樂意了谐算,就該給你扔個(gè)錯(cuò)誤出來了(not found module...)熟尉。

找不著,不干了洲脂!
JavaScript模塊的編譯

有幾個(gè)疑惑點(diǎn)斤儿,每個(gè)模塊里都存在 requireexports腮考,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)建模塊帮碰。 (文件模塊即第三方自己下載的模塊

依賴層級(jí)關(guān)系

通常,文件模塊不推薦直接調(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)潔、友好了猴抹。

os原生模塊引入流程

編寫核心模塊

本人功力還不到火候带族,這一節(jié)就直接跳過了,有能力的朋友可自行研究啦蟀给。


慚愧慚愧
模塊調(diào)用棧

C/C++ 內(nèi)建模塊屬于最底層模塊蝙砌,它屬于核心模塊,主要提供 API 給 JavaScript 核心模塊和第三方的文件模塊調(diào)用跋理。

JavaScript 核心模塊主要有兩個(gè)職責(zé):

  • 作為C/C++ 內(nèi)建模塊的封裝層和橋接層择克,供文件模塊調(diào)用
  • 純粹的功能模塊
模塊間調(diào)用關(guān)系

文件模塊就是第三方模塊,自己編寫的模塊前普,不要被它搞暈呦肚邢。

包與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ā)者!

獲取更多干貨分享艾恼,歡迎來搞住涉!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钠绍,隨后出現(xiàn)的幾起案子舆声,更是在濱河造成了極大的恐慌,老刑警劉巖柳爽,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件媳握,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡磷脯,警方通過查閱死者的電腦和手機(jī)蛾找,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來争拐,“玉大人腋粥,你說我怎么就攤上這事〖懿埽” “怎么了隘冲?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長绑雄。 經(jīng)常有香客問我展辞,道長,這世上最難降的妖魔是什么万牺? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任罗珍,我火速辦了婚禮,結(jié)果婚禮上脚粟,老公的妹妹穿的比我還像新娘覆旱。我一直安慰自己,他們只是感情好核无,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布扣唱。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪噪沙。 梳的紋絲不亂的頭發(fā)上炼彪,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音正歼,去河邊找鬼辐马。 笑死,一個(gè)胖子當(dāng)著我的面吹牛局义,可吹牛的內(nèi)容都是我干的喜爷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼旭咽,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼贞奋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起穷绵,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤轿塔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后仲墨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勾缭,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年目养,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俩由。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡癌蚁,死狀恐怖幻梯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情努释,我是刑警寧澤碘梢,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站伐蒂,受9級(jí)特大地震影響煞躬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逸邦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一恩沛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缕减,春花似錦雷客、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妓局。三九已至总放,卻和暖如春呈宇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背局雄。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工甥啄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炬搭。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓蜈漓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宫盔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子融虽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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