commonJS詳解

commonJS概述php

全部代碼都運行在模塊做用域,不會污染全局做用域。
模塊能夠屢次加載,可是隻會在第一次加載時運行一次,而後運行結果就被緩存了,之後再加載,就直接讀取緩存結果。要想讓模塊再次運行,必須清除緩存。
模塊加載的順序,按照其在代碼中出現的順序。html

2.module對象node

每一個模塊內部,都有一個module對象,表明當前模塊。它有如下屬性。編程

1json

console.log(module.id) //模塊的識別符,一般是帶有絕對路徑的模塊文件名。console.log(module.filename) //模塊的文件名,帶有絕對路徑。console.log(module.loaded) //返回一個布爾值,表示模塊是否已經完成加載。console.log(module.parent) //返回一個對象,表示調用該模塊的模塊。console.log(module.children) //返回一個數組,表示該模塊要用到的其餘模塊。console.log(module.exports) //表示模塊對外輸出的值。數組

2.1 module.exports屬性瀏覽器

module.exports屬性表示當前模塊對外輸出的接口,其餘文件加載該模塊,實際上就是讀取module.exports變量。緩存

2.2 exports變量bash

爲了方便,Node爲每一個模塊提供一個exports變量,指向module.exports。這等同在每一個模塊頭部,有一行這樣的命令:服務器

1

var exports = module.exports;(commonJS隱式作了這個賦值)

這樣作的好處是,在對外輸出模塊接口時,能夠向exports對象添加方法暴露出去。
所以若是改變了module.exports,但還想使用export.xxx的方式暴露一些東西,那就只好咱們本身來寫exports = module.exports;
慄:

1

module.exports = songthing;//接下來把exports指回來exports = module.exports;//常見寫法是:exports = module.exports = something;

3.AMD規範與CommonJS規範的兼容性

CommonJS規範加載模塊是同步的,只有加載完成,才能執行後面的操做。AMD規範則是非同步加載模塊,容許指定回調函數。因爲Node.js主要用於服務器編程,模塊文件通常都已經存在於本地硬盤,因此加載起來比較快,不用考慮非同步加載的方式,因此CommonJS規範比較適用。可是,若是是瀏覽器環境,要從服務器端加載模塊,這時就必須採用非同步模式,所以瀏覽器端通常採用AMD規範。

1

2

3

4

5

define(['package/lib'], function(lib){  function foo(){

    lib.log('hello world!');

  return {    foo: foo

  };

});

AMD規範容許輸出的模塊兼容CommonJS規範,這時define方法須要寫成下面這樣:

1

2

3

4

5

6

7

8

define(function (require, exports, module){  var someModule = require("someModule");  var anotherModule = require("anotherModule");

  someModule.doTehAwesome();

  anotherModule.doMoarAwesome();

  exports.asplode = function (){

    someModule.doTehAwesome();

    anotherModule.doMoarAwesome();

  };

});

4.require命令

4.1基本用法

Node使用CommonJS模塊規範,內置的require命令用於加載模塊文件。
require命令的基本功能是,讀入並執行一個JavaScript文件,而後返回該模塊的exports對象。若是沒有發現指定模塊,會報錯。(說白了就是將另外一個文件中暴露的值,引用到本文件中。)

1

2

3

4

5

// example.jsvar invisible = function () {  console.log("invisible");

}

exports.message = "hi";

exports.say = function () {  console.log(message);

}

運行下面的命令,能夠輸出exports對象。

1

//someelse.jsvar example = require('./example.js');console.log(example);// {//   message: "hi",//   say: [Function]// }

若是模塊輸出的是一個函數,那就不能定義在exports對象上面,而要定義在module.exports變量上面。

1

2

//example2.jsmodule.exports = function () {  console.log("hello world")

}require('./example2.js')()

上面代碼中,require命令調用自身,等因而執行module.exports,所以會輸出 hello world。

4.2加載規則

require命令用於加載文件,後綴名默認爲.js。

1

var foo = require('foo');//  等同於var foo = require('foo.js');

根據參數的不一樣格式,require命令去不一樣路徑尋找模塊文件:

(1)若是參數字符串以「/」開頭,則表示加載的是一個位於絕對路徑的模塊文件。好比,require('/home/marco/foo.js')將加載/home/marco/foo.js。
(2)若是參數字符串以「./」開頭,則表示加載的是一個位於相對路徑(跟當前執行腳本的位置相比)的模塊文件。好比,require('./circle')將加載當前腳本同一目錄的circle.js。
(3)若是參數字符串不以「./「或」/「開頭,則表示加載的是一個默認提供的核心模塊(位於Node的系統安裝目錄中),或者一個位於各級node_modules目錄的已安裝模塊(全局安裝或局部安裝)。

舉例來講,腳本/home/user/projects/foo.js執行了require('bar.js')命令,Node會依次搜索如下文件。

1

2

3

4

5

/usr/local/lib/node/bar.js

/home/user/projects/node_modules/bar.js

/home/user/node_modules/bar.js

/home/node_modules/bar.js

/node_modules/bar.js

這樣設計的目的是,使得不一樣的模塊能夠將所依賴的模塊本地化。
(4)若是參數字符串不以「./「或」/「開頭,並且是一個路徑,好比require('example-module/path/to/file'),則將先找到example-module的位置,而後再以它爲參數,找到後續路徑。
(5)若是指定的模塊文件沒有發現,Node會嘗試爲文件名添加.js、.json、.node後,再去搜索。.js件會以文本格式的JavaScript腳本文件解析,.json文件會以JSON格式的文本文件解析,.node文件會以編譯後的二進制文件解析。
(6)若是想獲得require命令加載的確切文件名,使用require.resolve()方法。

4.3目錄的加載規則

一般,咱們會把相關的文件會放在一個目錄裏面,便於組織。這時,最好爲該目錄設置一個入口文件,讓require方法能夠經過這個入口文件,加載整個目錄。
在目錄中放置一個package.json文件,而且將入口文件寫入main字段。下面是一個例子。

1

2

// package.json{ "name" : "some-library",  "main" : "./lib/some-library.js" }

require發現參數字符串指向一個目錄之後,會自動查看該目錄的package.json文件,而後加載main字段指定的入口文件。若是package.json文件沒有main字段,或者根本就沒有package.json文件,則會加載該目錄下的index.js文件或index.node文件。

4.4模塊的緩存

第一次加載某個模塊時,Node會緩存該模塊。之後再加載該模塊,就直接從緩存取出該模塊的module.exports屬性。

1

require('./example.js');require('./example.js').message = "hello";require('./example.js').message// "hello"

上面代碼中,連續三次使用require命令,加載同一個模塊。第二次加載的時候,爲輸出的對象添加了一個message屬性。可是第三次加載的時候,這個message屬性依然存在,這就證實require命令並無從新加載模塊文件,而是輸出了緩存。
若是想要屢次執行某個模塊,可讓該模塊輸出一個函數,而後每次require這個模塊的時候,從新執行一下輸出的函數。
全部緩存的模塊保存在require.cache之中,若是想刪除模塊的緩存,能夠像下面這樣寫。

1

2

// 刪除指定模塊的緩存delete require.cache[moduleName];// 刪除全部模塊的緩存Object.keys(require.cache).forEach(function(key) {  delete require.cache[key];

})

注意,緩存是根據絕對路徑識別模塊的,若是一樣的模塊名,可是保存在不一樣的路徑,require命令仍是會從新加載該模塊。

4.5環境變量NODE_PATH

Node執行一個腳本時,會先查看環境變量NODE_PATH。它是一組以冒號分隔的絕對路徑。在其餘位置找不到指定模塊時,Node會去這些路徑查找。

1

2

能夠將NODE_PATH添加到.bashrc。

export NODE_PATH="/usr/local/lib/node"

因此,若是遇到複雜的相對路徑,好比下面這樣:

1

var myModule = require('../../../../lib/myModule');

有兩種解決方法,一是將該文件加入node_modules目錄,二是修改NODE_PATH環境變量,package.json文件能夠採用下面的寫法。

1

2

"name": "node_path""version": "1.0.0""description": """main": "index.js""scripts": {    "start": "NODE_PATH=lib node index.js"

  },  "author": """license": "ISC"}

NODE_PATH是歷史遺留下來的一個路徑解決方案,一般不該該使用,而應該使用node_modules目錄機制。

4.6模塊的循環加載

若是發生模塊的循環加載,即A加載B,B又加載A,則B將加載A的不完整版本。

1

2

3

// a.jsexports.x = 'a1';console.log('a.js ', require('./b.js').x);

exports.x = 'a2';// b.jsexports.x = 'b1';console.log('b.js ', require('./a.js').x);

exports.x = 'b2';// main.jsconsole.log('main.js ', require('./a.js').x);console.log('main.js ', require('./b.js').x);

上面代碼是三個JavaScript文件。其中,a.js加載了b.js,而b.js又加載a.js。這時,Node返回a.js的不完整版本,因此執行結果以下。(也就是說,雖然這樣去require看似會形成a.js和b.js循環引用,但commonJS會在將循環的點剪斷循環,並對剪斷處所在的a.js終止執行,b獲得了第一個x值。)

1

2

3

4

5

$ node main.js

b.js  a1

a.js  b2

main.js  a2

main.js  b2

修改main.js,再次加載a.js和b.js。

1

// main.jsconsole.log('main.js ', require('./a.js').x);console.log('main.js ', require('./b.js').x);console.log('main.js ', require('./a.js').x);console.log('main.js ', require('./b.js').x);

執行上面代碼,結果以下。

1

2

3

4

5

6

7

$ node main.js

b.js  a1

a.js  b2

main.js  a2

main.js  b2

main.js  a2

main.js  b2

上面代碼中,第二次加載a.js和b.js時,會直接從緩存讀取exports屬性,因此a.js和b.js內部的console.log語句都不會執行了。

4.7 require.main

require方法有一個main屬性,能夠用來判斷模塊是直接執行,仍是被調用執行。
直接執行的時候(node module.js),require.main屬性指向模塊自己。

1

require.main === module// true

調用執行的時候(經過require加載該腳本執行),上面的表達式返回false。

相關文章
相關標籤/搜索