相較於原生的JavaScript,不一樣的JavaScript文件之間很難共享變量。有鑑於此,Node.js在JavaScript的基礎上進行了擴充,引入了require,exports,module三個global object。javascript
Smashing Node.js 的做者將node.js 中的modules 分紅了兩類,一類是absolute modules,一類是 relative modules。java
<1> absolute modules,指的是是node core自帶的重要modules,如http,fs等,咱們使用這些modules時,只須要 require(‘module_name’)便可;還包括用npm安裝的第三方module,這些module 默認安裝的位置是./node_modules/ 路徑下,使用這些modules時,一樣只須要require(‘module_name’)便可。可是,在package.json文件中要添加這些module的name,以便使用npm安裝。node
<2> relative modules,指的是咱們本身寫的modules,這些modules通常存在於工程文件夾內部,引用時咱們須要以require(‘相對路徑/module_name’)的方式引用。相對路徑,指的是這些modules相對工程文件夾的存放的位置。npm
注意:即便本身編寫的modules位於package.json相同的位置,也須要使用require('./module_name')來引用,不然會按照第一種方式尋找且出錯。json
經過這些modules,咱們能夠將複雜的node.js javascript代碼分割在不一樣的文件中,簡化咱們的工程的管理與維護。緩存
每一個module中的變量,除了用exports或者module.exports 聲明的屬性,都是局部變量,只用用於本模塊內。 ide
Modules are cached after the first time they are loaded. This means (among other things) that every call to require('foo') will get exactly the same object returned, if it would resolve to the same file. Multiple calls to require('foo') may not cause the module code to be executed multiple times. This is an important feature. With it, "partially done" objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles. If you want to have a module execute code multiple times, then export a function, and call that function.
這裏的require,跟咱們普通的理解的加載不同,除了把被加載模塊的經過module.exports 導出的object或者function放到執行環境中去,也會在加載的過程當中執行這個模塊的其餘代碼,由於js是解釋型語言,語意上是一句一句執行的。函數
node.js 使用require引入模塊時,會歷經如下四個步驟。因爲核心模塊已經被編譯執行過,因此核心模塊只會執行第四步。對於用戶編寫的非核心模塊,如下四步都會執行。因此,若是模塊內有須要執行的js語句,會在第一次require的時候執行。oop
因爲node.js 的緩存機制,每一個模塊都只會被加載一次,也即執行一次,即便屢次require。如要想這個module的代碼被屢次執行,要將這些代碼放到function中去,並經過module.exports 引出。測試
<1> 簡單的require
module_a.js
1 name_a = 'a'; //全局做用域
var a = 'a'; //執行做用域僅限於 module_a.js中 2 console.log(name_a); //第一次require的時候執行。 3 //console.log(name_b);//會出錯,name_b未定義
module_b.js
1 name_b ='b' 2 console.log(name_b);
main.js
1 require('./module_a'); //relative module,須要給出module的路徑, module_a除了被導出的object,未被導出的代碼也會被執行。 2 require('./module_b'); 3 //console.log(a); --> 出錯,不能訪問未被引出來的模塊變量。 4 console.log(name_a); 5 console.log(name_b);
<2> circular require
circular require:顧名思義,循環導入,a.js 要導入b.js, 同理b.js 也要導入a.js。咱們先看一下官方給出的例子
1 filename: a.js 2 3 console.log('a starting'); 4 exports.done = false; 5 var b = require('./b.js'); 6 console.log('in a, b.done = %j', b.done); 7 exports.done = true; 8 console.log('a done');
1 filename: b.js 2 3 4 console.log('b starting'); 5 exports.done = false; 6 var a = require('./a.js'); 7 console.log('in b, a.done = %j', a.done); 8 exports.done = true; 9 console.log('b done');
1 filename: main.js 2 3 console.log('main starting'); 4 var a = require('./a.js'); 5 var b = require('./b.js'); 6 console.log('in main, a.done=%j, b.done=%j', a.done, b.done);
When main.js
loads a.js
, then a.js
in turn loads b.js
. At that point, b.js
tries to load a.js
. In order to prevent an infinite loop an unfinished copy of the a.js
exports object is returned to the b.js
module. b.js
then finishes loading, and its exports
object is provided to the a.js
module.
當main.js 加載a.js的時候,a.js 又會加載b.js。b.js加載的過程當中又會嘗試加載a.js。爲了防止陷入無限的循環之中,b.js會直接引用已經加載可是尚未徹底加載的a.js(require ('b.js') 以前的代碼)。當b.js加載並執行完成後,a.js纔會接着加載執行,而後main.js加載執行。
測試結果 $ 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
每個模塊(js文件)都有一個局部變量:module,這個module指向當前的模塊。這個module有多個成員field,其中一個就是module.exports。module.exports 是一個javascript object,會被引出,返回給經過require加載的模塊。因此咱們只須要把要返回的object或者function賦給module.exports,就能夠返回任意咱們想要返回的object或者function了。
<1> 何時該用exports,何時該用module.exports
1 The exports variable that is available within a module starts as a reference to module.exports.
As with any variable, if you assign a new value to it, it is no longer bound to the previous value. 2 3 4 function require(...) { 5 // ... 6 function (module, exports) { 7 // Your module code here 8 exports = some_func; // re-assigns exports, exports is no longer 9 // a shortcut, and nothing is exported. 10 module.exports = some_func; // makes your module export 0 11 } (module, module.exports); 12 return module; 13 } 14 15 As a guideline, if the relationship between exports and module.exports seems like magic to you, ignore exports and only use module.exports.
上面是摘抄自官方的關於exports和module.exports 的解釋。 exports就是模塊內對module.exports 的一個引用,因此咱們給這個引用增長任何的屬性(exports.field = value),最後在其餘模塊require的時候均可以訪問到。
可是若是給直接給exports 賦予某個object(exports = object),試圖返回一個object的話,是不能經過exports來返回的,由於 exports = object這個過程就會把exports的指向給改變,再也不指向module.exports。而咱們用require 加載的object是module.exports 指向的object。
<2> 經過exports 返回對象的實例:
默認,每一個module經過exports返回一個空的對象實例,咱們能夠經過給該module的exports增長屬性來改變該module返回的對象實例。
1 module_a.js 2 3 exports.name = ‘john’; 4 exports.data = ‘this is some data’; 5 var privateVariable = 5; 6 exports.getPrivate = function () { 7 return privateVariable; 8 };
1 index.js 2 3 var a = require(‘./module_a’); //注意,exports默認返回的是一個已經建立好的對象實例,因此能夠直接調用屬性,而不須要new。 4 console.log(a.name); 5 console.log(a.data); 6 console.log(a.getPrivate());
<3> 經過module.exports 返回constructor 函數:
雖然經過module.exports能夠傳遞建立好的對象實例,但有時候咱們但願在傳遞時能控制這個對象的建立過程,此時咱們須要傳遞constructor函數:
1 module.exports = Person; 2 3 function Person(name){ 4 this.name = name; 5 } 6 7 Person.prototype.talk = function() { 8 console.log('my name is ', this.name); 9 }
1 var Person = require(‘./person’); 2 var john = new Person(‘john’); //Person 爲constructor函數,必須new以後才能使用 3 john.talk();