第6天筆記 nodejs 模塊-1

模塊

Node 有簡(jiǎn)單的模塊加載系統(tǒng)。在 Node 里,文件和模塊是一一對(duì)應(yīng)的。下面例子里,foo.js加載同一個(gè)文件夾里的circle.js模塊。

foo.js內(nèi)容:

var circle = require('./circle.js');

console.log( 'The area of a circle of radius 4 is '

+ circle.area(4));

circle.js內(nèi)容:

var PI = Math.PI;

exports.area = function (r) {

return PI * r * r;

};

exports.circumference = function (r) {

return 2 * PI * r;

};

circle.js模塊輸出了area()和circumference()函數(shù)。想要給根模塊添加函數(shù)和對(duì)象,你可以將他們添加到特定的exports對(duì)象。

加載到模塊里的變量是私有的,仿佛模塊是包含在一個(gè)函數(shù)里。在這個(gè)例子里,PI是circle.js的私有變量。

如果你想模塊里的根像一個(gè)函數(shù)一樣的輸出(比如 構(gòu)造函數(shù)),或者你想輸出一個(gè)完整對(duì)象,那就分派給module.exports,而不是exports。

bar.js使用square模塊, 它輸出了構(gòu)造函數(shù):

var square = require('./square.js');

var mySquare = square(2);

console.log('The area of my square is ' + mySquare.area());

square定義在square.js文件里:

// assigning to exports will not modify module, must use module.exports

module.exports = function(width) {

return {

area: function() {

return width * width;

}

};

}

模塊系統(tǒng)在require("module")模塊里實(shí)現(xiàn)。

Cycles

環(huán)形調(diào)用require(),當(dāng)返回時(shí)模塊可能都沒(méi)執(zhí)行結(jié)束。

考慮以下場(chǎng)景:

a.js:

console.log('a starting');

exports.done = false;

var b = require('./b.js');

console.log('in a, b.done = %j', b.done);

exports.done = true;

console.log('a done');

b.js:

console.log('b starting');

exports.done = false;

var a = require('./a.js');

console.log('in b, a.done = %j', a.done);

exports.done = true;

console.log('b done');

main.js:

console.log('main starting');

var a = require('./a.js');

var b = require('./b.js');

console.log('in main, a.done=%j, b.done=%j', a.done, b.done);

當(dāng)main.js加載a.js,a.js加載b.js。此時(shí),b.js試著加載a.js。為了阻止循環(huán)調(diào)用,a.js輸出對(duì)象的不完全拷貝返回給b.js模塊。b.js會(huì)結(jié)束加載,并且它的exports對(duì)象提供給a.js模塊。

main.js加載完兩個(gè)模塊時(shí),它們都會(huì)結(jié)束。這個(gè)程序的輸出如下:

$ node main.js

main starting

a starting

b starting

in b, a.done = false

b done

in a, b.done = true

a done

in main, a.done=true, b.done=true

如果你的程序有環(huán)形模塊依賴,需要保證是線性的。

核心模塊

Node 有很多模塊編譯成二進(jìn)制。這些模塊在本文檔的其他地方有更詳細(xì)的描述。

核心模塊定義在 Node 的源代碼lib/目錄里。

require()總是會(huì)優(yōu)先加載核心模塊。例如,require('http')總是返回編譯好的 HTTP 模塊,而不管這個(gè)文件的名字。

文件模塊

如果按照文件名沒(méi)有找到模塊,那么 Node 會(huì)試著加載添加了后綴.js,.json的文件,如果還沒(méi)好到,再試著加載添加了后綴.node的文件。

.js會(huì)解析為 JavaScript 的文本文件,.json會(huì)解析為 JSON 文本文件,.node會(huì)解析為編譯過(guò)的插件模塊,由dlopen負(fù)責(zé)加載。

模塊的前綴'/'表示絕對(duì)路徑。例如require('/home/marco/foo.js')將會(huì)加載/home/marco/foo.js文件。

模塊的前綴'./'表示相對(duì)于調(diào)用require()的路徑。就是說(shuō),circle.js必須和foo.js在 同一個(gè)目錄里,require('./circle')才能找到。

文件前沒(méi)有/或./前綴,表示模塊可能是core module,或者已經(jīng)從node_modules文件夾里加載過(guò)了。

如果指定的路徑不存在,require()將會(huì)拋出一個(gè)code屬性為'MODULE_NOT_FOUND'的異常。

從node_modules目錄里加載

如傳遞給require()的模塊不是一個(gè)本地模塊,并且不以'/','../', 或'./'開(kāi)頭,那么 Node 會(huì)從當(dāng)前模塊的父目錄開(kāi)始,嘗試在它的node_modules文件夾里加載模塊。

如果沒(méi)有找到,那么會(huì)到父目錄,直到到文件系統(tǒng)的根目錄里找。

例如,如果'/home/ry/projects/foo.js'里的文件加載require('bar.js'),那么 Node 將會(huì)按照下面的順序查找:

/home/ry/projects/node_modules/bar.js

/home/ry/node_modules/bar.js

/home/node_modules/bar.js

/node_modules/bar.js

這樣允許程序獨(dú)立,不會(huì)產(chǎn)生沖突。

可以請(qǐng)求指定的文件或分布子目錄里的模塊,在模塊名后添加路徑后綴。例如,require('example-module/path/to/file')會(huì)解決path/to/file相對(duì)于example-module的加載位置。路徑后綴使用相同語(yǔ)法。

文件夾作為模塊

可以把程序和庫(kù)放到獨(dú)立的文件夾里,并提供單一的入口指向他們。有三種方法可以將文件夾作為參數(shù)傳給require()。

第一個(gè)方法是,在文件夾的根創(chuàng)建一個(gè)package.json文件,它指定了main模塊。package.json的例子如下:

{ "name" : "some-library",

"main" : "./lib/some-library.js" }

如果這是在./some-library里的文件夾,require('./some-library')將會(huì)試著加載./some-library/lib/some-library.js。

如果文件夾里沒(méi)有package.json文件,Node 會(huì)試著加載index.js或index.node文件。例如,如果上面的例子里沒(méi)有 package.json 文件。那么require('./some-library')將會(huì)試著加載:

./some-library/index.js

./some-library/index.node

緩存

模塊第一次加載后會(huì)被被緩存。這就是說(shuō),每次調(diào)用require('foo')都會(huì)返回同一個(gè)對(duì)象,當(dāng)然,必須每次都要解析到同一個(gè)文件。

多次調(diào)用require('foo')也許不會(huì)導(dǎo)致模塊代碼多次執(zhí)行。這是很重要的特性,這樣就可以返回 "partially done" 對(duì)象,允許加載過(guò)渡性的依賴關(guān)系,即使可能會(huì)引起環(huán)形調(diào)用。

如果你希望多次調(diào)用一個(gè)模塊,那么就輸出一個(gè)函數(shù),然后調(diào)用這個(gè)函數(shù)。

模塊換成預(yù)警

模塊的緩存依賴于解析后的文件名。因此隨著調(diào)用位置的不同,模塊可能解析到不同的文件(例如,從node_modules文件夾加載)。如果解析為不同的文件,require('foo')可能會(huì)返回不同的對(duì)象。

module對(duì)象

{Object}

在每個(gè)模塊中,變量module是一個(gè)代表當(dāng)前模塊的對(duì)象的引用。為了方便,module.exports可以通過(guò)exports全局模塊訪問(wèn)。module不是事實(shí)上的全局對(duì)象,而是每個(gè)模塊內(nèi)部的。

module.exports

{Object}

模塊系統(tǒng)創(chuàng)建module.exports對(duì)象。很多人希望自己的模塊是某個(gè)類的實(shí)例。因此,把將要導(dǎo)出的對(duì)象賦值給module.exports。注意,將想要的對(duì)象賦值給exports,只是簡(jiǎn)單的將它綁定到本地exports變量,這可能并不是你想要的。

例如,假設(shè)我們有一個(gè)模塊叫a.js。

var EventEmitter = require('events').EventEmitter;

module.exports = new EventEmitter();

// Do some work, and after some time emit

// the 'ready' event from the module itself.

setTimeout(function() {

module.exports.emit('ready');

}, 1000);

另一個(gè)文件可以這么寫(xiě):

var a = require('./a');

a.on('ready', function() {

console.log('module a is ready');

});

注意,賦給module.exports必須馬上執(zhí)行,并且不能在回調(diào)中執(zhí)行。

x.js:

setTimeout(function() {

module.exports = { a: "hello" };

}, 0);

y.js:

var x = require('./x');

console.log(x.a);

exports alias

exports變量在引用到module.exports的模塊里可用。和其他變量一樣,如果你給他賦一個(gè)新的值,它不再指向老的值。

為了展示這個(gè)特性,假設(shè)實(shí)現(xiàn):require():

function require(...) {

// ...

function (module, exports) {

// Your module code here

exports = some_func;? ? ? ? // re-assigns exports, exports is no longer

// a shortcut, and nothing is exported.

module.exports = some_func; // makes your module export 0

} (module, module.exports);

return module;

}

如果你對(duì)exports和module.exports間的關(guān)系感到迷糊,那就只用module.exports就好。

module.require(id)

id{String}

返回: {Object} 已經(jīng)解析模塊的module.exports

module.require方法提供了一種像require()一樣從最初的模塊加載一個(gè)模塊的方法。

為了能這樣做,你必須獲得module對(duì)象的引用。require()返回module.exports,并且module是一個(gè)典型的只能在特定模塊作用域內(nèi)有效的變量,如果要使用它,就必須明確的導(dǎo)出。

module.id

{String}

模塊的標(biāo)識(shí)符。通常是完全解析的文件名。

module.filename

{String}

模塊完全解析的文件名。

module.loaded

{Boolean}

模塊是已經(jīng)加載完畢,還是在加載中。

module.parent

{Module Object}

引入這個(gè)模塊的模塊。

module.children

{Array}

由這個(gè)模塊引入的模塊。

其他...

為了獲取即將用require()加載的準(zhǔn)確文件名,可以使用require.resolve()函數(shù)。

綜上所述,下面用偽代碼的高級(jí)算法形式演示了 require.resolve 的工作流程:

require(X) from module at path Y

1. If X is a core module,

a. return the core module

b. STOP

2. If X begins with './' or '/' or '../'

a. LOAD_AS_FILE(Y + X)

b. LOAD_AS_DIRECTORY(Y + X)

3. LOAD_NODE_MODULES(X, dirname(Y))

4. THROW "not found"

LOAD_AS_FILE(X)

1. If X is a file, load X as JavaScript text.? STOP

2. If X.js is a file, load X.js as JavaScript text.? STOP

3. If X.json is a file, parse X.json to a JavaScript Object.? STOP

4. If X.node is a file, load X.node as binary addon.? STOP

LOAD_AS_DIRECTORY(X)

1. If X/package.json is a file,

a. Parse X/package.json, and look for "main" field.

b. let M = X + (json main field)

c. LOAD_AS_FILE(M)

2. If X/index.js is a file, load X/index.js as JavaScript text.? STOP

3. If X/index.json is a file, parse X/index.json to a JavaScript object. STOP

4. If X/index.node is a file, load X/index.node as binary addon.? STOP

LOAD_NODE_MODULES(X, START)

1. let DIRS=NODE_MODULES_PATHS(START)

2. for each DIR in DIRS:

a. LOAD_AS_FILE(DIR/X)

b. LOAD_AS_DIRECTORY(DIR/X)

NODE_MODULES_PATHS(START)

1. let PARTS = path split(START)

2. let I = count of PARTS - 1

3. let DIRS = []

4. while I >= 0,

a. if PARTS[I] = "node_modules" CONTINUE

c. DIR = path join(PARTS[0 .. I] + "node_modules")

b. DIRS = DIRS + DIR

c. let I = I - 1

5. return DIRS

從全局文件夾加載

如果環(huán)境變量NODE_PATH設(shè)置為冒號(hào)分割的絕對(duì)路徑列表,并且在模塊在其他地方?jīng)]有找到,Node 將會(huì)搜索這些路徑。(注意,Windows 里,NODE_PATH用分號(hào)分割 )。

另外, Node 將會(huì)搜索這些路徑。

1:$HOME/.node_modules

2:$HOME/.node_libraries

3:$PREFIX/lib/node

$HOME是用戶的 home 文件夾,$PREFIX是 Node 里配置的node_prefix。

這大多是歷史原因照成的。強(qiáng)烈建議將所以來(lái)的模塊放到node_modules文件夾里。這樣加載會(huì)更快。

訪問(wèn)主模塊

當(dāng) Node 運(yùn)行一個(gè)文件時(shí),require.main就會(huì)設(shè)置為它的module。也就是說(shuō)你可以通過(guò)測(cè)試判斷文件是否被直接運(yùn)行。

require.main === module

對(duì)于foo.js文件。 如果直接運(yùn)行node foo.js,返回true, 如果通過(guò)require('./foo')是間接運(yùn)行。

因?yàn)閙odule提供了filename屬性(通常等于__filename),程序的入口點(diǎn)可以通過(guò)檢查require.main.filename來(lái)獲得。

附錄: 包管理技巧

Node 的require()函數(shù)語(yǔ)義定義的足夠通用,它能支持各種常規(guī)目錄結(jié)構(gòu)。諸如dpkg,rpm, 和npm包管理程序,不用修改就可以從 Node 模塊構(gòu)建本地包。

下面我們介紹一個(gè)可行的目錄結(jié)構(gòu):

假設(shè)我們有一個(gè)文件夾/usr/lib/node//,包含指定版本的包內(nèi)容。

一個(gè)包可以依賴于其他包。為了安裝包 foo,可能需要安裝特定版本的bar包。bar包可能有自己的包依賴,某些條件下,依賴關(guān)系可能會(huì)發(fā)生沖突或形成循環(huán)。

因?yàn)?Node 會(huì)查找他所加載的模塊的realpath(也就是說(shuō)會(huì)解析符號(hào)鏈接),然后按照上文描述的方式在 node_modules 目錄中尋找依賴關(guān)系,這種情形跟以下體系結(jié)構(gòu)非常相像:

/usr/lib/node/foo/1.2.3/-foo包, version 1.2.3.

/usr/lib/node/bar/4.3.2/-foo依賴的bar包內(nèi)容

/usr/lib/node/foo/1.2.3/node_modules/bar- 指向/usr/lib/node/bar/4.3.2/的符號(hào)鏈接

/usr/lib/node/bar/4.3.2/node_modules/*- 指向bar包所依賴的包的符號(hào)鏈接

因此,即使存在循環(huán)依賴或依賴沖突,每個(gè)模塊還可以獲得他所依賴的包得可用版本。

當(dāng)foo包里的代碼調(diào)用foo,將會(huì)獲得符號(hào)鏈接/usr/lib/node/foo/1.2.3/node_modules/bar指向的版本。然后,當(dāng) bar 包中的代碼調(diào)用require('queue'),將會(huì)獲得符號(hào)鏈接/usr/lib/node/bar/4.3.2/node_modules/quux指向的版本。

另外,為了讓模塊搜索更快些,不要將包直接放在/usr/lib/node目錄中,而是將它們放在/usr/lib/node_modules//目錄中。 這樣在依賴的包找不到的情況下,就不會(huì)一直尋找 /usr/node_modules目錄或/node_modules目錄了?;谡{(diào)用 require() 的文件所在真實(shí)路徑,因此包本身可以放在任何位置。

為了讓 Node 模塊對(duì) Node REPL 可用,可能需要將/usr/lib/node_modules文件夾路徑添加到環(huán)境變量$NODE_PATH。由于模塊查找$NODE_PATH文件夾都是相對(duì)路徑,因此包可以放到任何位置。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • topics: 1.The Node.js philosophy 2.The reactor pattern 3....
    宮若石閱讀 1,126評(píng)論 0 1
  • Node.js是目前非常火熱的技術(shù),但是它的誕生經(jīng)歷卻很奇特。 眾所周知,在Netscape設(shè)計(jì)出JavaScri...
    w_zhuan閱讀 3,639評(píng)論 2 41
  • 模塊是構(gòu)建應(yīng)用程序的基礎(chǔ),也使得函數(shù)和變量私有化,不直接對(duì)外暴露出來(lái),接下來(lái)我們就要介紹Node的模塊化系統(tǒng)和它最...
    一個(gè)胖子的我閱讀 605評(píng)論 0 1
  • 個(gè)人入門(mén)學(xué)習(xí)用筆記、不過(guò)多作為參考依據(jù)。如有錯(cuò)誤歡迎斧正 目錄 簡(jiǎn)書(shū)好像不支持錨點(diǎn)、復(fù)制搜索(反正也是寫(xiě)給我自己看...
    kirito_song閱讀 2,500評(píng)論 1 37
  • 1 Node.js模塊的實(shí)現(xiàn) 之前在網(wǎng)上查閱了許多介紹Node.js的文章,可惜對(duì)于Node.js的模塊機(jī)制大都著...
    zlx_2017閱讀 1,288評(píng)論 0 1