Nodejs 模塊機制

nodejs 模塊機制

簡單模塊定義和使用

在Node.js中尖淘,定義一個模塊十分方便。我們以計算圓形的面積和周長兩個方法為例忍宋,來表現(xiàn)Node.js中模塊的定義方式俗或。

var PI = Math.PI;
exports.area = function (r) {
    return PI * r * r;
};
exports.circumference = function (r) {
    return 2 * PI * r;
};

將這個文件存為circle.js,然后新建一個app.js文件陨亡,并寫入以下代碼:

var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is ' + circle.area(4));

可以看到模塊調(diào)用也十分方便傍衡,只需要require需要調(diào)用的文件即可深员。

在require了這個文件之后,定義在exports對象上的方法便可以隨意調(diào)用蛙埂。Node.js將模塊的定義和調(diào)用都封裝得極其簡單方便辨液,從API對用戶友好這一個角度來說,Node.js的模塊機制是非常優(yōu)秀的箱残。

關(guān)于exports的內(nèi)容,可以參考之前的文章 exports && module.exports

模塊分類

核心模塊

核心模塊優(yōu)先級僅次于緩存加載止吁,因此無法加載一個和核心模塊標(biāo)識符相同的自定義模塊被辑。

路徑形式的文件模塊

以"."、".."開頭和"/"開始的標(biāo)識符敬惦,這里都被當(dāng)作文件模塊來處理盼理。require()方法會將路徑轉(zhuǎn)為真實路徑,并以真實路徑作為索引俄删,并將編譯執(zhí)行后的結(jié)果存放到緩存中宏怔。

自定義模塊(特殊的文件模塊)

自定義模塊是指非核心模塊,也不是路徑形式的標(biāo)識符畴椰。它是一種特殊的文件模塊臊诊,可能是一個文件或者包的形式。
模塊路徑是Node在定位文件模塊的具體文件時制定的查找策略斜脂,具體表現(xiàn)為一個路徑組成的數(shù)組(module.paths)抓艳。這個路徑由當(dāng)前目錄開始往上一直到根目錄,Node會逐個嘗試模塊路徑中的路徑帚戳,直到找到目標(biāo)文件未知玷或,若到達根目錄還是沒有找到目標(biāo)文件,則會拋出查找失敗的異常片任。當(dāng)前文件的目錄越深偏友,模塊查找耗時越多隧土。

模塊載入策略

上文中說道享怀,Node.js的模塊分為兩類,一類為原生(核心)模塊质和,一類為文件模塊犁钟。

原生模塊在Node.js源代碼編譯的時候編譯進了二進制執(zhí)行文件棱诱,加載的速度最快。另一類文件模塊是動態(tài)加載的涝动,加載速度比原生模塊慢迈勋。但是Node.js對原生模塊和文件模塊都進行了緩存,于是在第二次require時醋粟,是不會有重復(fù)開銷的靡菇。由于通過命令行加載啟動的文件幾乎都為文件模塊重归。我們從Node.js如何加載文件模塊開始談起。

我們從命令行啟動上文的app.js文件厦凤。

node app.js

加載文件模塊的工作鼻吮,主要由原生模塊module來實現(xiàn)和完成,該原生模塊在啟動時已經(jīng)被加載较鼓,進程直接調(diào)用到runMain靜態(tài)方法椎木。

// bootstrap main module.
Module.runMain = function () {
    // Load the main module--the command line argument.
    Module._load(process.argv[1], null, true);
};

_load靜態(tài)方法在分析文件名之后執(zhí)行

var module = new Module(id, parent);

并根據(jù)文件路徑緩存當(dāng)前模塊對象,該模塊實例對象則根據(jù)文件名加載博烂。

module.load(filename);

實際上在文件模塊中香椎,又分為3類模塊。這三類文件模塊以后綴來區(qū)分禽篱,Node.js會根據(jù)后綴名來決定加載方法畜伐。

  • .js。通過fs模塊同步讀取js文件并編譯執(zhí)行躺率。
  • .node玛界。通過C/C++進行編寫的Addon。通過dlopen方法進行加載悼吱。
  • .json慎框。讀取文件,調(diào)用JSON.parse解析加載舆绎。

這里我們將詳細描述js后綴的編譯過程鲤脏。Node.js在編譯js文件的過程中實際完成的步驟有對js文件內(nèi)容進行頭尾包裝。以app.js為例吕朵,包裝之后的app.js將會變成以下形式:

(function (exports, require, module, __filename, __dirname) {
    var circle = require('./circle.js');
    console.log('The area of a circle of radius 4 is ' + circle.area(4));
});

這段代碼會通過vm原生模塊的runInThisContext方法執(zhí)行(類似eval猎醇,只是具有明確上下文,不污染全局)努溃,返回為一個具體的function對象硫嘶。最后傳入module對象的exportsrequire方法梧税,module沦疾,__filename(文件名),__dirname(目錄名)作為實參并執(zhí)行第队。

這就是為什么require并沒有定義在app.js文件中哮塞,但是這個方法卻存在的原因。從Node.js的API文檔中可以看到還有__filename凳谦、__dirname忆畅、moduleexports幾個沒有定義但是卻存在的變量尸执。

__filename``和__dirname在查找文件路徑的過程中分析得到后傳入的家凯。module變量是這個模塊對象自身缓醋,exports是在module的構(gòu)造函數(shù)中初始化的一個空對象({},而不是null)绊诲。

在這個主文件中送粱,可以通過require方法去引入其余的模塊。而其實這個require方法實際調(diào)用的就是load方法掂之。

load方法在載入抗俄、編譯、緩存了module后世舰,返回moduleexports對象橄镜。這就是circle.js文件中只有定義在exports對象上的方法才能被外部調(diào)用的原因。

以上所描述的模塊載入機制均定義在lib/module.js中冯乘。

require 方法中的文件查找策略

盡管require方法極其簡單,但是內(nèi)部的加載卻是十分復(fù)雜的晒夹,其加載優(yōu)先級也各自不同裆馒。

image1.jpg-29.2kB
image1.jpg-29.2kB

從文件加載

當(dāng)文件模塊緩存中不存在,而且不是原生模塊的時候丐怯,Node.js會解析require方法傳入的參數(shù)喷好,并從文件系統(tǒng)中加載實際的文件,加載過程中的包裝和編譯細節(jié)在前一節(jié)中已經(jīng)介紹過读跷,這里我們將詳細描述查找文件模塊的過程梗搅,其中,也有一些細節(jié)值得知曉效览。

require方法接受以下幾種參數(shù)的傳遞:

  • http无切、fs、path等丐枉,原生模塊哆键。
  • ./mod或../mod,相對路徑的文件模塊瘦锹。
  • /pathtomodule/mod籍嘹,絕對路徑的文件模塊。
  • mod弯院,非原生模塊的文件模塊辱士。

在進入路徑查找之前有必要描述一下module path這個Node.js中的概念。對于每一個被加載的文件模塊听绳,創(chuàng)建這個模塊對象的時候颂碘,這個模塊便會有一個paths屬性,其值根據(jù)當(dāng)前文件的路徑計算得到辫红。我們創(chuàng)建modulepath.js這樣一個文件凭涂,其內(nèi)容為:

console.log(module.paths);

我們將其放到任意一個目錄中執(zhí)行node modulepath.js命令祝辣,將得到以下的輸出結(jié)果(mac的演示結(jié)果)。

[ '/Users/beifeng/Desktop/test_node/node_modules',
  '/Users/beifeng/Desktop/node_modules',
  '/Users/beifeng/node_modules',
  '/Users/node_modules',
  '/node_modules' ]

可以看出module path的生成規(guī)則為:從當(dāng)前文件目錄開始查找node_modules目錄切油;然后依次進入父目錄蝙斜,查找父目錄下的node_modules目錄;依次迭代澎胡,直到根目錄下的node_modules目錄孕荠。

文件模塊查找流程

image2.jpg-69.9kB
image2.jpg-69.9kB

簡而言之,如果require絕對路徑的文件攻谁,查找時不會去遍歷每一個node_modules目錄稚伍,其速度最快。其余流程如下:

1.從module paths數(shù)組中取出第一個目錄作為查找基準(zhǔn)戚宦。
2.直接從目錄中查找該文件个曙,如果存在,則結(jié)束查找受楼。如果不存在垦搬,則進行下一條查找
3.嘗試添加.js.json艳汽、.node后綴后查找猴贰,如果存在文件,則結(jié)束查找河狐。如果不存在米绕,則進行下一條。
4.嘗試將require的參數(shù)作為一個包來進行查找馋艺,讀取目錄下的package.json文件栅干,取得main參數(shù)指定的文件。
5.嘗試查找該文件捐祠,如果存在非驮,則結(jié)束查找。如果不存在雏赦,則進行第3條查找劫笙。
6.如果繼續(xù)失敗,則取出module path數(shù)組中的下一個目錄作為基準(zhǔn)查找星岗,循環(huán)第1至5個步驟填大。
7.如果繼續(xù)失敗,循環(huán)第1至6個步驟俏橘,直到module paths中的最后一個值允华。
8.如果仍然失敗,則拋出異常。

整個查找過程十分類似原型鏈的查找和作用域的查找靴寂。所幸Node.js對路徑查找實現(xiàn)了緩存機制磷蜀,否則由于每次判斷路徑都是同步阻塞式進行,會導(dǎo)致嚴(yán)重的性能消耗百炬。

CommonJS規(guī)范

JavaScript缺少包結(jié)構(gòu)褐隆。CommonJS致力于改變這種現(xiàn)狀,于是定義了包的結(jié)構(gòu)規(guī)范(http://wiki.commonjs.org/wiki/Packages/1.0 )剖踊。

CommonJS(http://www.commonjs.org)規(guī)范的出現(xiàn)庶弃,其目標(biāo)是為了構(gòu)建JavaScript在包括Web服務(wù)器,桌面德澈,命令行工具歇攻,及瀏覽器方面的生態(tài)系統(tǒng)。

一個符合CommonJS規(guī)范的包應(yīng)該是如下這種結(jié)構(gòu):

  • 一個package.json文件應(yīng)該存在于包頂級目錄下
  • 二進制文件應(yīng)該包含在bin目錄下梆造。
  • JavaScript代碼應(yīng)該包含在lib目錄下缴守。
  • 文檔應(yīng)該在doc目錄下。
  • 單元測試應(yīng)該在test目錄下镇辉。

由上文的require的查找過程可以知道斧散,Node.js在沒有找到目標(biāo)文件時,會將當(dāng)前目錄當(dāng)作一個包來嘗試加載摊聋,所以在package.json文件中最重要的一個字段就是main。而實際上栈暇,這一處是Node.js的擴展麻裁,標(biāo)準(zhǔn)定義中并不包含此字段,對于require源祈,只需要main屬性即可煎源。

引用文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市香缺,隨后出現(xiàn)的幾起案子手销,更是在濱河造成了極大的恐慌,老刑警劉巖图张,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锋拖,死亡現(xiàn)場離奇詭異,居然都是意外死亡祸轮,警方通過查閱死者的電腦和手機兽埃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來适袜,“玉大人柄错,你說我怎么就攤上這事。” “怎么了售貌?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵给猾,是天一觀的道長。 經(jīng)常有香客問我颂跨,道長敢伸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任毫捣,我火速辦了婚禮详拙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蔓同。我一直安慰自己饶辙,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布斑粱。 她就那樣靜靜地躺著弃揽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪则北。 梳的紋絲不亂的頭發(fā)上矿微,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機與錄音尚揣,去河邊找鬼涌矢。 笑死,一個胖子當(dāng)著我的面吹牛快骗,可吹牛的內(nèi)容都是我干的娜庇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼方篮,長吁一口氣:“原來是場噩夢啊……” “哼名秀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起藕溅,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤匕得,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后巾表,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汁掠,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年集币,在試婚紗的時候發(fā)現(xiàn)自己被綠了调塌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡惠猿,死狀恐怖羔砾,靈堂內(nèi)的尸體忽然破棺而出负间,到底是詐尸還是另有隱情,我是刑警寧澤姜凄,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布政溃,位于F島的核電站,受9級特大地震影響态秧,放射性物質(zhì)發(fā)生泄漏董虱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一申鱼、第九天 我趴在偏房一處隱蔽的房頂上張望愤诱。 院中可真熱鬧,春花似錦捐友、人聲如沸淫半。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽科吭。三九已至,卻和暖如春猴鲫,著一層夾襖步出監(jiān)牢的瞬間对人,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工拂共, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留牺弄,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓宜狐,卻偏偏與公主長得像势告,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肌厨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

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

  • 說明:該學(xué)習(xí)筆記參考《深入淺出Node.js》在學(xué)習(xí)過程中,添加了自己的理解和適當(dāng)?shù)难a充豁陆!僅供參考柑爸! NodeJs...
    秋意思寒閱讀 689評論 0 1
  • 模塊 Node 有簡單的模塊加載系統(tǒng)。在 Node 里盒音,文件和模塊是一一對應(yīng)的表鳍。下面例子里,foo.js加載同一個...
    保川閱讀 589評論 0 0
  • 1 Node.js模塊的實現(xiàn) 之前在網(wǎng)上查閱了許多介紹Node.js的文章,可惜對于Node.js的模塊機制大都著...
    zlx_2017閱讀 1,221評論 0 1
  • 1 Node.js模塊的實現(xiàn)# 之前在網(wǎng)上查閱了許多介紹Node.js的文章,可惜對于Node.js的模塊機制大都...
    七寸知架構(gòu)閱讀 2,053評論 1 50
  • Node.js是目前非诚榉蹋火熱的技術(shù)譬圣,但是它的誕生經(jīng)歷卻很奇特。 眾所周知雄坪,在Netscape設(shè)計出JavaScri...
    w_zhuan閱讀 3,609評論 2 41