模組(Modules)
stability:5 -locked
node有一個簡單的模組載入機制.在node裡面檔案和模組是 一對一 對應的.例如,foo.js載入在同一檔案夾下的circle.js模組.
foo.js的內容:
[javascript]
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
+ circle.area(4));</SPAN>
var circle = require('./circle.js');
console.log( 'The area of a circle of radius 4 is '
+ circle.area(4)); circle.js的內容:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">var PI = Math.PI;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
};</SPAN>
var PI = Math.PI;
exports.area = function (r) {
return PI * r * r;
};
exports.circumference = function (r) {
return 2 * PI * r;
}; circle.js模組以匯出了area()方法和circumference()方法.為了匯出一個對象,需要加上指定的exports對象.
屬於模組的變數是私人的.在這個例子中變數PI就是私人於circle.js.
模組機制是在require('module')模組中實現的.
環形載入(Cycles)
當存在環形的require()調用時,當一個模組被返回被執行的時候,可能還沒有載入完成,考慮下面這種情景:
a.js:
[javascript]
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">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');</SPAN>
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:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">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');</SPAN>
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:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">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);</SPAN>
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); 當main.js載入a.js的時候,a.js依次需要載入b.js.在這時,b.js試著去載入a.js.為了防止一個無窮的迴圈(loop),一個未完成的a.js的副本的exports匯出對象被返回到b.js模組.然後b.js完成載入,然後他的exports匯出對象被提供給a.js模組.
當main.js載入完這兩個模組的時候,他們都已經載入完.這個程式的輸出應該這樣子:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">$ 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</SPAN>
$ 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 如果你的程式裡面有環形的模組依賴,確保制定相應的計劃(意思是,確保在所有依賴的模組都載入完時在調用,避免在模組未載入完時,調用導致程式不可預期的結果).
(迴圈載入,require()方法有對應的機制,上面例子中b.js返回時,b.js載入的a.js沒有載入完,但是當b.js載入完時,a.js就載入完了,這時候b.js裡面儲存的a.js也會被更新成完成的對象,其實我覺得儲存的可能就是類似於a.js的引用.)
核心模組(Core modules)
node中有一些模組被編譯成二進位. 這些模組在文檔的其他地方有更詳細的描述.
核心模組被定義在node的源碼中的lib檔案夾下.
如果核心模組的標識符被傳入require()方法時,總是被優先載入.例如,require('http')將總是返回內建的http模組,即使當前有一個同名的模組檔案夾.
檔案模組載入機制(File modules)
如果準確的檔案名稱字沒有找到,node將會試圖依次載入添加了.js,.json,.node等尾碼名的檔案.
.js檔案被當作javascript文字檔解讀,.json檔案被轉換成json文字檔. .node檔案被當做已經編譯好的外掛程式模組,使用dloopen載入.(在dlopen()函數以指定模式開啟指定的動態串連庫檔案,並返回一個控制代碼給調用進程。使用dlclose()來卸載開啟的庫。)
當一個模組的首碼是"/"時,表示一個模組檔案的絕對路徑.例如:require('/home/marco/foo.js')將會使用'/home/marco/foo.js'作為路徑載入.
當一個模組的首碼是"./"時,表示一個模組檔案的相對路徑調用require().例如:為了讓foo.js中的require('./circle.js')能被找到,circle.js必須和foo.js在同一目錄下.
如果沒有"./"或者"/"當檔案首碼,這個模組不是一個核心模組,就是一個需要從node_modules檔案夾中載入的模組.
如果給定的載入路徑不存在,requier()方法將會拋出一個Error錯誤對象,並且Error的code屬性被設定成:'MODULE_NOT_FOUND'.
從node_modules檔案夾載入模組(loading from node_mudoles folders)
如果傳入到require()方法中的模組標識符不是本地模組,並且也不是"/","../",或者"./"開頭,node則會在當前模組的父目錄路徑上追加/node_modules,以這個路徑試圖載入模組.
如果這裡也沒有找到,node會繼續移動到更上一層的父目錄,等等...直到到達根目錄.
例如,檔案'/home/ry/project/foo.js'中調用require('bar.js'),node會尋找以下列出的路徑,依次是:
/home/ry/projects/node_modules/bar.js
/home/ry/node_modules/bar.js
/home/node_modules/bar.js
/node_modules/bar.js
這將允許程式本地化他們的依賴,以便不產生衝突.
檔案夾就是模組(Floder as modules)
組織程式和類庫放進自己包含的目錄下是很方便的,然後提供一個單獨的入口指定這個類庫,有三種方法把一個檔案夾作為一個參數傳進require()方法.
第一種方法是在根目錄建立一個package.json檔案,指定一個主模組.一個paceage.json的例子看起來像這樣子:
[javascript]
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">{ "name" : "some-library",
"main" : "./lib/some-library.js" }</SPAN>
{ "name" : "some-library",
"main" : "./lib/some-library.js" } 如果當前程式路徑是在./some-library目錄下,則require('./some-library')將會試圖載入路徑./some-library/lib/some-library.js.
這是node自動拓展自package.json檔案的結果.
如果這個目錄下沒有出現package.json檔案,node將會試圖在這個目錄載入一個index.js或者index.node檔案出來.例如,如果在上面的例子中沒有package.json檔案,則require('./sone-library')將會試圖載入:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">./some-library/index.js
./some-library/index.node</SPAN>
./some-library/index.js
./some-library/index.node 緩衝(Caching)
當模組被第一次載入之後就被緩衝了起來,這意味著在其他地方調用require('foo')將會獲得一樣一樣的對象被返回,如果他們掃描的是同一檔案的情況下.
多次調用require('foo')不會引起模組代碼被執行多次.這是一個重要的特性.有了它,部分載入完成的對象可以被返回,進而允許載入過渡期的依賴對象,甚至當他們引起環形載入.
(上面這段話,其實就是對本文剛開始介紹的環形載入的一種說明,就是說,在node中require()方法載入對象時,可能還沒載入完就返回了,但是返回的是一個索引而已,在一個node應用中,使用require()載入一個模組,都會先在緩衝中尋找,有就返回,沒有在載入.)
如果你想要一個模組的代碼被執行多次,你可以匯出一個方法,然後調用這個方法.
緩衝模組提醒(Module Caching caveats)
模組是以他們被解析出的檔案名稱為基礎緩衝起來的,因為模組可能由於調用require()方法的當前路徑不一樣,而導致解析出不一樣的檔案名稱,(從node_modules檔案夾載入),如果解析出不同的檔案,將不能保證require('foo')總是會返回一致的對象.
module對象(The module Object)
在每一個模組中,自由變數module是一個代表當前模組的引用.因此module.exports和exports對象是一樣的.module實際上不是一個全域變數,但是內建於每一個模組中.
module.exports
exports對象是被模組機制建立的,有時候,許多情況需要讓模組成為一些類的一個執行個體,而使exports不可訪問,為了把渴望匯出的對象分配給moduel.exports對象,在例子中假設我們需要編寫一個a.js模組.[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">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);</SPAN>
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);在另一個檔案裡我們需要做:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">var a = require('./a');
a.on('ready', function() {
console.log('module a is ready');
});</SPAN>
var a = require('./a');
a.on('ready', function() {
console.log('module a is ready');
});注意,這種指派至到module.exports對象的操作一定要立刻執行,如果放在任何一個回呼函數裡面,將不會生效.
x.js[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">setTimeout(function() {
module.exports = { a: "hello" };
}, 0);</SPAN>
setTimeout(function() {
module.exports = { a: "hello" };
}, 0);y.js[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">var x = require('./x');
console.log(x.a);</SPAN>
var x = require('./x');
console.log(x.a);
轉載請註明出處:http://blog.csdn.net/qq413041153/article/details/7922126
module.require(id)
id String
return:從解析出的模組中匯出的對象.
module.require()方法提供了一種方式去載入一個在之前已經載入過的模組.
注意,為了實現這種方式,你必須獲得一個這個模組的索引,自從require()方法返回了exports對象之後,這個模組只是在這個一段指定的代碼裡有效,為了使用你需要要明確的匯出他.
module.id
String
模組的標識符,是一個能代表模組的完整路徑
module.filename
String
一個能代表模組的完整路徑
module.loaded
boolean
代表這個模組是載入完成還是正在載入中.
module.parent
module object
代表載入當前模組的模組.
module.children
Array
代表當前模組載入的子模組.
總結(All together...)
為了在調用require()時獲得準確的檔案名稱被載入,使用require.resolve()方法.
上面的代碼放在一起,下面是require.resolve()方法的進階演算法的虛擬碼:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">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.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.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 ROOT = index of first instance of "node_modules" in PARTS, or 0
3. let I = count of PARTS - 1
4. let DIRS = []
5. while I > ROOT,
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
6. return DIRS</SPAN>
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.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.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 ROOT = index of first instance of "node_modules" in PARTS, or 0
3. let I = count of PARTS - 1
4. let DIRS = []
5. while I > ROOT,
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
6. return DIRS 從通用檔案夾載入模組(loading from global folders)
如果NODE_PATH環境變數被設定成一個以逗號分割的絕對路徑列表,node如果沒有在其他地方找到模組將會搜尋這些路徑,(注意:在windows中NODE_PATH是以分好為分隔字元.)
另外,node將會搜尋一下路徑:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">1: $HOME/.node_modules
2: $HOME/.node_libraries
3: $PREFIX/lib/node</SPAN>
1: $HOME/.node_modules
2: $HOME/.node_libraries
3: $PREFIX/lib/node$HOME是使用者的home目錄,$PREFIX是node配置的node_prefix.
這些基本上都是一些曆史遺留問題.建議你把你的依賴放進node_modules檔案夾中,這樣的話,載入更快,更可靠.
訪問主模組(Accessing the main module)
當一個檔案是直接從node執行的,那麼require.main變數會設定成module對象,這意味著你可以在測試時判斷一個模組是否是直接啟動並執行.[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">require.main === module</SPAN>
require.main === module
例如foo.js,如果通過node foo.js運行,將會返回true,如果通過require('./foo'),將會返回false.
因為module提供了一個filename對象,(正常情況下和_filename等價),當前程式的進入點可以通過require.main.filename獲得.
附錄:包管理建議(Package Manager Tips)
理論上,require()方法被設計成一般情況下,可以滿足載入若干合理的檔案結構目錄.包管理程式例如:dbkg,rpm,and npm,在沒有修改的情況下,是可以從node Modules中載入,建立本地packages.
下面我們給出了一些建議的目錄結構:
比方說,我們想在目錄:/user/lib/node/<some-package>/<some-version>下儲存一個包的指定版本,
packages可以依賴另一個,為了安裝包foo,你可能不得不安裝bar包的一個指定版本.而且bar包可能還有自己的一些依賴,在某種情況下,這些依賴可能有衝突或者形成環形載入.
自從node尋找它載入的每一個模組的真是路徑,然後就像上面描述的那樣,在node_modules中尋找他們的依賴,用下面的這種結構,這種情況很簡單就可以解決:[javascript] view plaincopyprint?
<SPAN style="FONT-SIZE: 18px; FONT-FAMILY: FangSong_GB2312">/usr/lib/node/foo/1.2.3/ - Contents of the foo package, version 1.2.3.
/usr/lib/node/bar/4.3.2/ - Contents of the bar package that foo depends on.
/usr/lib/node/foo/1.2.3/node_modules/bar - Symbolic link to /usr/lib/node/bar/4.3.2/.
/usr/lib/node/bar/4.3.2/node_modules/* - Symbolic links to the packages that bar depends on.</SPAN>
/usr/lib/node/foo/1.2.3/ - Contents of the foo package, version 1.2.3.
/usr/lib/node/bar/4.3.2/ - Contents of the bar package that foo depends on.
/usr/lib/node/foo/1.2.3/node_modules/bar - Symbolic link to /usr/lib/node/bar/4.3.2/.
/usr/lib/node/bar/4.3.2/node_modules/* - Symbolic links to the packages that bar depends on.
因此,即使發生環形載入,或者依賴衝突,每個模組都能夠獲得一個他們能夠使用的版本.
擋在foo包中執行require('bar'),將會通過解析出來的路徑串連字元/user/lib/node/foo/1.2.3/node_modules/bar獲得指定版本的bar.然後,當bar包中執行require('quux')時,將會通過解析出來的路徑串連字元/user/lib/node/bar/4.3.2/node_modules/quux獲得指定版本的quux.
此外,為了讓模組的尋找過程更理想,不要直接把包放在/usr/lib/node下,我們可以把他們放在/usr/lib/node_modules/<name>/<version>下.這樣node就可以不用麻煩的在/usr/node_modules/或者/node_modules下尋找找不到的包.
為了確保模組在repl中是可見的,將/usr/lib/node_modules添加到$NODE_PATH環境變數中可能會起到效果.因為模組查到用到的其他模組全部是相對的,並且require()的 載入也是基於真實路徑的,所以,包可以放在任何地方.