node學(xué)習(xí)筆記第八節(jié):模塊化

早在Netscape誕生不久后豹绪,JavaScript就一直在探索本地編程的路损痰,Rhino是其代表產(chǎn)物女坑。無奈那時服務(wù)端JavaScript走的路均是參考眾多服務(wù)器端語言來實現(xiàn)的尘盼,在這樣的背景之下肖油,一沒有特色兼吓,二沒有實用價值。但是隨著JavaScript在前端的應(yīng)用越來越廣泛森枪,以及服務(wù)端JavaScript的推動视搏,JavaScript現(xiàn)有的規(guī)范十分薄弱审孽,不利于JavaScript大規(guī)模的應(yīng)用。那些以JavaScript為宿主語言的環(huán)境中浑娜,只有本身的基礎(chǔ)原生對象和類型佑力,更多的對象和API都取決于宿主的提供,所以筋遭,我們可以看到JavaScript缺少這些功能:

JavaScript沒有模塊系統(tǒng)打颤。沒有原生的支持密閉作用域或依賴管理。
JavaScript沒有標(biāo)準(zhǔn)庫漓滔。除了一些核心庫外编饺,沒有文件系統(tǒng)的API禁荸,沒有IO流API等辖所。
JavaScript沒有標(biāo)準(zhǔn)接口。沒有如Web Server或者數(shù)據(jù)庫的統(tǒng)一接口羽氮。
JavaScript沒有包管理系統(tǒng)豁鲤。不能自動加載和安裝依賴秽誊。
于是便有了CommonJS(http://www.commonjs.org)規(guī)范的出現(xiàn),其目標(biāo)是為了構(gòu)建JavaScript在包括Web服務(wù)器琳骡,桌面锅论,命令行工具,及瀏覽器方面的生態(tài)系統(tǒng)楣号。
CommonJS制定了解決這些問題的一些規(guī)范棍厌,而Node.js就是這些規(guī)范的一種實現(xiàn)。Node.js自身實現(xiàn)了require方法作為其引入模塊的方法竖席,同時NPM也基于CommonJS定義的包規(guī)范,實現(xiàn)了依賴管理和模塊自動安裝等功能敬肚。

一毕荐,CommonJS的模塊規(guī)范

Node與瀏覽器以及 W3C組織、CommonJS組織艳馒、ECMAScript之間的關(guān)系

Node借鑒CommonJS的Modules規(guī)范實現(xiàn)了一套模塊系統(tǒng)憎亚,所以先來看看CommonJS的模塊規(guī)范。

CommonJS對模塊的定義十分簡單弄慰,主要分為模塊引用第美、模塊定義和模塊標(biāo)識3個部分。

1. 模塊引用

模塊引用的示例代碼如下:

var math = require('math');

在CommonJS規(guī)范中陆爽,存在require()方法什往,這個方法接受模塊標(biāo)識,以此引入一個模塊的API到當(dāng)前上下文中慌闭。

2. 模塊定義

在模塊中别威,上下文提供require()方法來引入外部模塊躯舔。對應(yīng)引入的功能,上下文提供了exports對象用于導(dǎo)出當(dāng)前模塊的方法或者變量省古,并且它是唯一導(dǎo)出的出口粥庄。在模塊中,還存在一個module對象豺妓,它代表模塊自身惜互,而exports是module的屬性。在Node中琳拭,一個文件就是一個模塊训堆,將方法掛載在exports對象上作為屬性即可定義導(dǎo)出的方式:

// math.js

exports.add = function () {  

var sum = 0,    i = 0,    args = arguments,    l = args.length;  

while (i < l) {    sum += args[i++];  } 

 return sum;

}; 

在另一個文件中,我們通過require()方法引入模塊后臀栈,就能調(diào)用定義的屬性或方法了:

// program.js

var math = require('math');

exports.increment = function (val) {  return math.add(val, 1);}; 

3.模塊標(biāo)識

模塊標(biāo)識其實就是傳遞給require()方法的參數(shù)蔫慧,它必須是符合小駝峰命名的字符串,或者以.权薯、..開頭的相對路徑姑躲,或者絕對路徑。它可以沒有文件名后綴.js盟蚣。模塊的定義十分簡單黍析,接口也十分簡潔。它的意義在于將類聚的方法和變量等限定在私有的作用域中屎开,同時支持引入和導(dǎo)出功能以順暢地連接上下游依賴阐枣。每個模塊具有獨立的空間,它們互不干擾奄抽,在引用時也顯得干凈利落蔼两。

二,Node的模塊實現(xiàn)

node加載模塊的具體過程

Node在實現(xiàn)中并非完全按照規(guī)范實現(xiàn)逞度,而是對模塊規(guī)范進行了一定的取舍额划,同時也增加了少許自身需要的特性。盡管規(guī)范中exports档泽、require和module聽起來十分簡單俊戳,但是Node在實現(xiàn)它們的過程中究竟經(jīng)歷了什么,這個過程需要知曉馆匿。
在Node中引入模塊抑胎,需要經(jīng)歷如下3個步驟。

1. 路徑分析

2. 文件定位

3. 編譯執(zhí)行

在Node中渐北,模塊分為兩類:一類是Node提供的模塊阿逃,稱為核心模塊;另一類是用戶編寫的模塊,稱為文件模塊盆昙。

? 核心模塊部分在Node源代碼的編譯過程中羽历,編譯進了二進制執(zhí)行文件。在Node進程啟動時淡喜,部分核心模塊就被直接加載進內(nèi)存中秕磷,所以這部分核心模塊引入時,文件定位和編譯執(zhí)行這兩個步驟可以省略掉炼团,并且在路徑分析中優(yōu)先判斷澎嚣,所以它的加載速度是最快的。

? 文件模塊則是在運行時動態(tài)加載瘟芝,需要完整的路徑分析易桃、文件定位、編譯執(zhí)行過程锌俱,速度比核心模塊慢晤郑。

1.優(yōu)先從緩存加載

與前端瀏覽器會緩存靜態(tài)腳本文件以提高性能一樣,Node對引入過的模塊都會進行緩存贸宏,以減少二次引入時的開銷造寝。不同的地方在于,瀏覽器僅僅緩存文件吭练,而Node緩存的是編譯和執(zhí)行之后的對象诫龙。不論是核心模塊還是文件模塊,require()方法對相同模塊的二次加載都一律采用緩存優(yōu)先的方式鲫咽,這是第一優(yōu)先級的签赃。不同之處在于核心模塊的緩存檢查先于文件模塊的緩存檢查。
模塊加載的優(yōu)先級是:緩存模塊 > 核心模塊 > 用戶自定義模塊分尸。

2.路徑分析和文件定位

因為標(biāo)識符有幾種形式锦聊,對于不同的標(biāo)識符,模塊的查找和定位有不同程度上的差異箩绍。

a.模塊標(biāo)識符分析

Node基于一個模塊標(biāo)識符進行模塊查找孔庭。模塊標(biāo)識符在Node中主要分為以下幾類。

核心模塊伶选,如http、fs尖昏、path等仰税。
.或..開始的相對路徑文件模塊。
以/開始的絕對路徑文件模塊抽诉。
非路徑形式的文件模塊陨簇,如自定義的connect模塊

? 核心模塊

核心模塊的優(yōu)先級僅次于緩存加載,它在Node的源代碼編譯過程中已經(jīng)編譯為二進制代碼,其加載過程最快河绽。如果試圖加載一個與核心模塊標(biāo)識符相同的自定義模塊己单,那是不會成功的。如果自己編寫了一個http用戶模塊耙饰,想要加載成功纹笼,必須選擇一個不同的標(biāo)識符或者換用路徑的方式。

? 路徑形式的文件模塊

以.苟跪、..和/開始的標(biāo)識符廷痘,這里都被當(dāng)做文件模塊來處理。在分析路徑模塊時件已,require()方法會將路徑轉(zhuǎn)為真實路徑笋额,并以真實路徑作為索引,將編譯執(zhí)行后的結(jié)果存放到緩存中篷扩,以使二次加載時更快兄猩。由于文件模塊給Node指明了確切的文件位置,所以在查找過程中可以節(jié)約大量時間鉴未,其加載速度慢于核心模塊枢冤。

? 自定義模塊

自定義模塊指的是非核心模塊,也不是路徑形式的標(biāo)識符歼狼。它是一種特殊的文件模塊掏导,可能是一個文件或者包的形式。這類模塊的查找是最費時的羽峰,也是所有方式中最慢的一種趟咆。

b.文件定位

從緩存加載的優(yōu)化策略使得二次引入時不需要路徑分析、文件定位和編譯執(zhí)行的過程梅屉,大大提高了再次加載模塊時的效率值纱。但在文件的定位過程中,還有一些細(xì)節(jié)需要注意坯汤,這主要包括文件擴展名的分析虐唠、目錄和包的處理。

? 文件擴展名分析

CommonJS模塊規(guī)范也允許在標(biāo)識符中不包含文件擴展名惰聂,這種情況下疆偿,Node會按.js、.json搓幌、.node的次序補足擴展名杆故,依次嘗試。在嘗試的過程中溉愁,需要調(diào)用fs模塊同步阻塞式地判斷文件是否存在处铛。因為Node是單線程的,所以這里是一個會引起性能問題的地方。小訣竅是:如果是.node和.json文件撤蟆,在傳遞給require()的標(biāo)識符中帶上擴展名奕塑,會加快一點速度。
require加載無文件類型的優(yōu)先級:.js > .json > .node

? 目錄分析和包

在分析標(biāo)識符的過程中家肯,require()通過分析文件擴展名之后龄砰,可能沒有查找到對應(yīng)文件,但卻得到一個目錄息楔,此時Node會將目錄當(dāng)做一個包來處理寝贡。

在這個過程中,Node對CommonJS包規(guī)范進行了一定程度的支持值依。首先圃泡,Node在當(dāng)前目錄下查找package.json(CommonJS包規(guī)范定義的包描述文件),通過JSON.parse()解析出包描述對象愿险,從中取出main屬性指定的文件名進行定位颇蜡。如果文件名缺少擴展名,將會進入擴展名分析的步驟辆亏。而如果main屬性指定的文件名錯誤风秤,或者壓根沒有package.json文件,Node會將index當(dāng)做默認(rèn)文件名扮叨,然后依次查找index.js缤弦、index.node、index.json彻磁。

如果在目錄分析的過程中沒有定位成功任何文件碍沐,則自定義模塊進入下一個模塊路徑進行查找。如果模塊路徑數(shù)組都被遍歷完畢衷蜓,依然沒有查找到目標(biāo)文件累提,則會拋出查找失敗的異常。

c.模塊編譯

在Node中磁浇,每個文件模塊都是一個對象斋陪,它的定義如下:


function Module(id, parent) {  
    this.id = id;  
    this.exports = {};  
    this.parent = parent; 
     if (parent && parent.children) {   
     parent.children.push(this);  
    }  
    this.filename = null; 
     this.loaded = false;  
    this.children = [];

編譯和執(zhí)行是引入文件模塊的最后一個階段。定位到具體的文件后置吓,Node會新建一個模塊對象无虚,然后根據(jù)路徑載入并編譯。對于不同的文件擴展名衍锚,其載入方法也有所不同友题,具體如下所示。
? .js文件构拳。

通過fs模塊同步讀取文件后編譯執(zhí)行咆爽。

? .node文件。

這是用C/C++編寫的擴展文件置森,通過dlopen()方法加載最后編譯生成的文件斗埂。

? .json文件。

通過fs模塊同步讀取文件后凫海,用JSON.parse()解析返回結(jié)果呛凶。

? 其余擴展名文件。

它們都被當(dāng)做.js文件載入行贪。

每一個編譯成功的模塊都會將其文件路徑作為索引緩存在Module._cache對象上漾稀,以提高二次引入的性能。

JavaScript模塊的編譯

回到CommonJS模塊規(guī)范建瘫,我們知道每個模塊文件中存在著require崭捍、exports、module這3個變量啰脚,但是它們在模塊文件中并沒有定義殷蛇,那么從何而來呢?甚至在Node的API文檔中橄浓,我們知道每個模塊中還有__filename粒梦、__dirname這兩個變量的存在,它們又是從何而來的呢荸实?如果我們把直接定義模塊的過程放諸在瀏覽器端匀们,會存在污染全局變量的情況。

事實上准给,在編譯的過程中泄朴,Node對獲取的JavaScript文件內(nèi)容進行了頭尾包裝。在頭部添加了(function (exports, require, module, __filename, __dirname) {\n圆存,在尾部添加了\n});叼旋。一個正常的JavaScript文件會被包裝成如下的樣子:

(function (exports, require, module, __filename, __dirname) {
  var math = require('math');
  exports.area = function (radius) {
    return Math.PI * radius * radius;
  };
});

這樣每個模塊文件之間都進行了作用域隔離。包裝之后的代碼會通過vm原生模塊的runInThisContext()方法執(zhí)行(類似eval沦辙,只是具有明確上下文夫植,不污染全局),返回一個具體的function對象油讯。最后详民,將當(dāng)前模塊對象的exports屬性、require()方法陌兑、module(模塊對象自身)沈跨,以及在文件定位中得到的完整文件路徑和文件目錄作為參數(shù)傳遞給這個function()執(zhí)行。

三兔综,包和NPM

在模塊之外饿凛,包和NPM則是將模塊聯(lián)系起來的一種機制狞玛。


image.png

CommonJS的包規(guī)范的定義其實也十分簡單,它由包結(jié)構(gòu)和包描述文件兩個部分組成涧窒,前者用于組織包中的各種文件心肪,后者則用于描述包的相關(guān)信息,以供外部讀取分析纠吴。

包結(jié)構(gòu)

包實際上是一個存檔文件硬鞍,即一個目錄直接打包為.zip或tar.gz格式的文件,安裝后解壓還原為目錄戴已。完全符合CommonJS規(guī)范的包目錄應(yīng)該包含如下這些文件固该。

package.json:包描述文件。
bin:用于存放可執(zhí)行二進制文件的目錄糖儡。
lib:用于存放JavaScript代碼的目錄伐坏。
doc:用于存放文檔的目錄。
test:用于存放單元測試用例的代碼握联。

包描述文件

包描述文件用于表達非代碼相關(guān)的信息著淆,它是一個JSON格式的文件——package.json,位于包的根目錄下拴疤,是包的重要組成部分永部。而NPM的所有行為都與包描述文件的字段息息相關(guān)。

這個可以看看NPM官網(wǎng)對package.json的定義規(guī)范呐矾。

可以通過npm adduser, npm publish把自己的package上傳到npm倉庫苔埋。

本文內(nèi)容源自https://blog.csdn.net/u012422829/article/details/52760981https://blog.csdn.net/qbian/article/details/79367500

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜒犯,一起剝皮案震驚了整個濱河市组橄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌罚随,老刑警劉巖玉工,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異淘菩,居然都是意外死亡遵班,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門潮改,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狭郑,“玉大人,你說我怎么就攤上這事汇在『踩” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵糕殉,是天一觀的道長亩鬼。 經(jīng)常有香客問我殖告,道長,這世上最難降的妖魔是什么雳锋? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任丛肮,我火速辦了婚禮,結(jié)果婚禮上魄缚,老公的妹妹穿的比我還像新娘。我一直安慰自己焚廊,他們只是感情好冶匹,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咆瘟,像睡著了一般嚼隘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袒餐,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天飞蛹,我揣著相機與錄音,去河邊找鬼灸眼。 笑死卧檐,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焰宣。 我是一名探鬼主播霉囚,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼匕积!你這毒婦竟也來了盈罐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤闪唆,失蹤者是張志新(化名)和其女友劉穎盅粪,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悄蕾,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡票顾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帆调。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片库物。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贷帮,靈堂內(nèi)的尸體忽然破棺而出戚揭,到底是詐尸還是另有隱情,我是刑警寧澤撵枢,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布民晒,位于F島的核電站精居,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏潜必。R本人自食惡果不足惜靴姿,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望磁滚。 院中可真熱鬧佛吓,春花似錦、人聲如沸垂攘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晒他。三九已至吱型,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陨仅,已是汗流浹背津滞。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灼伤,地道東北人触徐。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像狐赡,于是被迫代替她去往敵國和親锌介。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

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