**CommonJS規(guī)范 **
早在Netscape誕生不久后耍共,JavaScript就一直在探索本地編程的路,Rhino是其代表產(chǎn)物。無(wú)奈那時(shí)服務(wù)端JavaScript走的路均是參考眾多服務(wù)器端語(yǔ)言來(lái)實(shí)現(xiàn)的砚殿,在這樣的背景之下,一沒(méi)有特色丈莺,二沒(méi)有實(shí)用價(jià)值蜗细。但是隨著JavaScript在前端的應(yīng)用越來(lái)越廣泛,以及服務(wù)端JavaScript的推動(dòng)官地,JavaScript現(xiàn)有的規(guī)范十分薄弱酿傍,不利于JavaScript大規(guī)模的應(yīng)用。那些以JavaScript為宿主語(yǔ)言的環(huán)境中驱入,只有本身的基礎(chǔ)原生對(duì)象和類(lèi)型赤炒,更多的對(duì)象和API都取決于宿主的提供,所以亏较,我們可以看到JavaScript缺少這些功能:
- JavaScript沒(méi)有模塊系統(tǒng)莺褒。沒(méi)有原生的支持密閉作用域或依賴管理。
- JavaScript沒(méi)有標(biāo)準(zhǔn)庫(kù)雪情。除了一些核心庫(kù)外遵岩,沒(méi)有文件系統(tǒng)的API,沒(méi)有IO流API等巡通。
- JavaScript沒(méi)有標(biāo)準(zhǔn)接口尘执。沒(méi)有如Web Server或者數(shù)據(jù)庫(kù)的統(tǒng)一接口。
- JavaScript沒(méi)有包管理系統(tǒng)宴凉。不能自動(dòng)加載和安裝依賴誊锭。
于是便有了CommonJS(http://www.commonjs.org)規(guī)范的出現(xiàn),其目標(biāo)是為了構(gòu)建JavaScript在包括Web服務(wù)器弥锄,桌面丧靡,命令行工具签孔,及瀏覽器方面的生態(tài)系統(tǒng)。CommonJS制定了解決這些問(wèn)題的一些規(guī)范窘行,而Node.js就是這些規(guī)范的一種實(shí)現(xiàn)饥追。Node.js自身實(shí)現(xiàn)了require方法作為其引入模塊的方法,同時(shí)NPM也基于CommonJS定義的包規(guī)范罐盔,實(shí)現(xiàn)了依賴管理和模塊自動(dòng)安裝等功能但绕。這里我們將深入一下Node.js的require機(jī)制和NPM基于包規(guī)范的應(yīng)用。
簡(jiǎn)單模塊定義和使用
在Node.js中惶看,定義一個(gè)模塊十分方便捏顺。我們以計(jì)算圓形的面積和周長(zhǎng)兩個(gè)方法為例,來(lái)表現(xiàn)Node.js中模塊的定義方式纬黎。
1 var PI = Math.PI;
2 exports.area = function (r) {
3 return PI * r * r;
4 };
5 exports.circumference = function (r) {
6 return 2 * PI * r;
7 };</pre>
}//歡迎加入全棧開(kāi)發(fā)交流圈一起學(xué)習(xí)交流:582735936
]//面向1-3年前端人員
} //幫助突破技術(shù)瓶頸幅骄,提升思維能力
將這個(gè)文件存為circle.js,并新建一個(gè)app.js文件本今,并寫(xiě)入以下代碼:
1 var circle = require('./circle.js');
2 console.log( 'The area of a circle of radius
3 is ' + circle.area(
4));</pre>
可以看到模塊調(diào)用也十分方便拆座,只需要require需要調(diào)用的文件即可。
在require了這個(gè)文件之后冠息,定義在exports對(duì)象上的方法便可以隨意調(diào)用挪凑。Node.js將模塊的定義和調(diào)用都封裝得極其簡(jiǎn)單方便,從API對(duì)用戶友好這一個(gè)角度來(lái)說(shuō)逛艰,Node.js的模塊機(jī)制是非常優(yōu)秀的躏碳。
模塊載入策略
Node.js的模塊分為兩類(lèi),一類(lèi)為原生(核心)模塊散怖,一類(lèi)為文件模塊菇绵。原生模塊在Node.js源代碼編譯的時(shí)候編譯進(jìn)了二進(jìn)制執(zhí)行文件,加載的速度最快镇眷。另一類(lèi)文件模塊是動(dòng)態(tài)加載的咬最,加載速度比原生模塊慢。但是Node.js對(duì)原生模塊和文件模塊都進(jìn)行了緩存偏灿,于是在第二次require時(shí)丹诀,是不會(huì)有重復(fù)開(kāi)銷(xiāo)的。其中原生模塊都被定義在lib這個(gè)目錄下面翁垂,文件模塊則不定性铆遭。
node app.js
由于通過(guò)命令行加載啟動(dòng)的文件幾乎都為文件模塊。我們從Node.js如何加載文件模塊開(kāi)始談起沿猜。加載文件模塊的工作枚荣,主要由原生模塊module來(lái)實(shí)現(xiàn)和完成,該原生模塊在啟動(dòng)時(shí)已經(jīng)被加載啼肩,進(jìn)程直接調(diào)用到runMain靜態(tài)方法橄妆。
1 // bootstrap main module.
2 Module.runMain = function () {
3 // Load the main module--the command line argument.
4 Module._load(process.argv[1], null, true); 5 };</pre>
_load靜態(tài)方法在分析文件名之后執(zhí)行
var module = new Module(id, parent);
并根據(jù)文件路徑緩存當(dāng)前模塊對(duì)象衙伶,該模塊實(shí)例對(duì)象則根據(jù)文件名加載。
module.load(filename);
實(shí)際上在文件模塊中害碾,又分為3類(lèi)模塊矢劲。這三類(lèi)文件模塊以后綴來(lái)區(qū)分,Node.js會(huì)根據(jù)后綴名來(lái)決定加載方法慌随。
- .js芬沉。通過(guò)fs模塊同步讀取js文件并編譯執(zhí)行。
- .node阁猜。通過(guò)C/C++進(jìn)行編寫(xiě)的Addon丸逸。通過(guò)dlopen方法進(jìn)行加載。
- .json剃袍。讀取文件黄刚,調(diào)用JSON.parse解析加載。
這里我們將詳細(xì)描述js后綴的編譯過(guò)程民效。Node.js在編譯js文件的過(guò)程中實(shí)際完成的步驟有對(duì)js文件內(nèi)容進(jìn)行頭尾包裝憔维。
以app.js為例,包裝之后的app.js將會(huì)變成以下形式:
1 (function (exports, require, module, __filename, __dirname) {
2 var circle = require('./circle.js');
3 console.log('The area of a circle of radius
4 is ' + circle.area(4)); 4 });</pre>
這段代碼會(huì)通過(guò)vm原生模塊的runInThisContext方法執(zhí)行(類(lèi)似eval研铆,只是具有明確上下文埋同,不污染全局),返回為一個(gè)具體的function對(duì)象棵红。最后傳入module對(duì)象的exports,require方法咧栗,module逆甜,文件名,目錄名作為實(shí)參并執(zhí)行致板。
這就是為什么require并沒(méi)有定義在app.js 文件中交煞,但是這個(gè)方法卻存在的原因。從Node.js的API文檔中可以看到還有__filename斟或、__dirname素征、module、exports幾個(gè)沒(méi)有定義但是卻存在的變量萝挤。其中__filename和__dirname在查找文件路徑的過(guò)程中分析得到后傳入的御毅。module變量是這個(gè)模塊對(duì)象自身,exports是在module的構(gòu)造函數(shù)中初始化的一個(gè)空對(duì)象({}怜珍,而不是null)端蛆。
在這個(gè)主文件中,可以通過(guò)require方法去引入其余的模塊酥泛。而其實(shí)這個(gè)require方法實(shí)際調(diào)用的就是load方法今豆。
load方法在載入嫌拣、編譯、緩存了module后呆躲,返回module的exports對(duì)象异逐。這就是circle.js文件中只有定義在exports對(duì)象上的方法才能被外部調(diào)用的原因。
以上所描述的模塊載入機(jī)制均定義在lib/module.js中插掂。