Node.js API —— Modules(模塊)

// 說明
    Node API 版本爲 v0.10.31。
    中文參考:http://nodeapi.ucdok.com/#/api/http://blog.sina.com.cn/oleoneoy
    本段爲博主註解。

目錄

模塊
    ○ Cycles
    ○ Core Modules
    ○ File Modules
    ○ Loading from node_modules Folders
    ○ Folders as Modules
    ○ Caching
        ■ Module Caching Caveats
    ○ The module Object
        ■ module.exports
            ■ exports.alias
        ■ module.require(id)
        ■ module.id
        ■ module.filename
        ■ module.loaded
        ■ module.parent
        ■ module.children
    ○ All Together...
    ○ Loading from the global folders
    ○ Accessing the main module
    ○ Addenda: Package Manager Tips

 模塊

  穩定性:5 - 鎖定
    Node 有一個簡單的模塊加載系統。在 Node 裏面,文件和模塊是一一對應的。舉個例子,foo.js 加載同一目錄下的模塊 circle.js
    foo.js 的內容以下:
1 var circle = require('./circle.js');
2 console.log('The area of a circle of radius 4 is '
3   + circle.area(4));

   circle.js 的內容以下:node

1 var PI = Math.PI;
2 
3 exports.area = function(r) {
4   return PI * r * r;
5 };
6 
7 exports.circumference = function(r) {
8   return 2 * PI * r;
9 };

    模塊 circle.js 導出了函數 area()circumference()。想把函數和對象添加到你的模塊的根節點,你能夠把它們添加到特殊的 exports 對象中。
    模塊的局部變量是私有的,好像模塊被一個函數包裹起來同樣。在這個例子裏面,變量 PIcircle.js 私有的。
    若是你想你的模塊的導出的根節點是一個函數(例如是一個構造函數)或者若是你想在一次指派就能導出一個完整的對象而不是每次都新建一個屬性,把它指派到 module.exports 而不是 exports 中。
    在下面,bar.js 使用了一個導出一個構造函數的 square 模塊:
算法

1 var square = require('./square.js');
2 var mySquare = square(2);
3 console.log('The area of my square is ' + mySquare.area());

    square 模塊定義在 square.js 中:npm

1 // assigning to exports will not modify module, must use module.exports
2 module.exports = function(width) {
3   return {
4     area: function() {
5       return width * width;
6     }
7   };
8 }

    模塊系統的實如今 require("module") 模塊中。json

Cycles(循環引用)

    當有循環的 require() 引用時,一個模塊可能返回時還沒被執行。
    考慮這樣的情形:
    a.js :
1 console.log('a starting');
2 exports.done = false;
3 var b = require('./b.js');
4 console.log('in a, b.done = %j', b.done);
5 exports.done = true;
6 console.log('a done');

    b.js :api

1 console.log('b starting');
2 exports.done = false;
3 var a = require('./a.js');
4 console.log('in b, a.done = %j', a.done);
5 exports.done = true;
6 console.log('b done');

   main.js :數組

1 console.log('main starting');
2 var a = require('./a.js');
3 var b = require('./b.js');
4 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。爲了防止無限的循環,a.js 的 exports 對象的一個未完成的拷貝返回給 b.js 模塊。而後 b.js 完成加載,並將它的 exports 對象提供給 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

    若是你在你的程序中有模塊的循環依賴,確保有依據地組織。函數

Core Modules(核心模塊)

    Node 有若干個編譯成二進制的模塊。這些模塊在本文檔其餘地方更加詳細地描述。
    核心模塊定義在 Node 的源文件 lib/ 目錄下。
    若是核心模塊的模塊標識符被傳入 require() ,它們老是優先加載。舉個例子, require('http') 將老是返回內建的 HTTP 模塊,即便有一個同名的文件。

File Modules(文件模塊)

    若是精確的文件名沒有找到文件,node 會試圖以 .js.json.node 的順序給引用的文件名添加後綴並加載它。
    .js 文件被做爲 JavaScript 文本文件解析,.json 文件被當成 JSON 文本文件解析,.node 文件當成編譯好的二進制 addon 模塊解析being使用 dlopen 加載。
    模塊以 '/' 爲前綴表示爲文件的絕對路徑。例如,require('/home/marco/foo.js') 將加載文件 /home/marco/foo.js
    模塊以 './' 爲前綴表示爲相對於調用 require() 的文件的相對路徑。也就是說,爲了 require('./circle') 能找到它,circle.js 必須和 foo.js 在同一目錄下。
    沒有前綴 '/' 和 './' 來表示是一個文件,該模塊會被認爲或者是一個「核心模塊」或者是從 node_modules 目錄加載的模塊。
    若是給出的路徑不存在,require() 將拋出一個 code 屬性設置爲 'MODULE_NOT_FOUND' 的錯誤。

Loading from node_modules Folders(從 node_modules 文件夾加載)

    若是傳遞給 require() 的模塊標識符不是一個核心模塊,而且也不是以 '/''../' 或 './' 開頭,那麼 node 將從當前模塊的父目錄開始,拼接上 /node_modules,並嘗試從那個位置加載模塊。
    若是在找不到,那麼將移到父目錄,依次類推,知道到達根目錄。
    例如,若是文件 '/home/ry/projects/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
    這能容許程序局部化它們的依賴,不至於產生衝突。

Folders as Modules(文件夾做爲模塊)

    將程序和庫組織成自包含的目錄,而後爲庫提供一個簡單的入口點是很方便的。有三個途徑可令文件夾能夠做爲參數傳進 require() 中。
    第一種是在文件夾的根目錄建立一個指定 main 模塊的 package.json 文件。一個 package.json 的例子以下:
1 { "name": "some-library",
2   "main": "./lib/some-library.js" }

    若是 package.json 文件在 ./some-library 文件夾中,那麼 require('./some-library') 將會嘗試加載 ./some-library/lib/some-library.js
    這是 Node 能感知 package.json 文件的程度。
    若是在文件夾中沒有 package.json 文件,那麼 node 將嘗試加載 index.jsindex.node 文件。例如,若是在上述例子中沒有 package.json 文件,那麼 require('./some-library') 將嘗試加載:
ui

  • ./some-library/index.js
  • ./some-library/index.node

Caching(緩存)

    模塊在第一次加載以後會被緩存起來。這意味着(除了其它例外)若是解析到同一個文件,每次調用 require('foo') 都會返回同一個對象。
    屢次調用 require('foo') 不會引發模塊代碼被執行屢次。這是個很重要的特性。有了它,「部分完成」的對象才能被返回,所以容許依賴能被及時加載,即便它們會引發循環引用。
    若是你想一個模塊會執行屢次代碼,那麼導出一個函數,並調用那個函數。

Module Caching Caveats(模塊緩存警告)

    模塊是根據它們解析過的文件名來緩存的。由於模塊能夠根據調用模塊的位置(從 node_modules 文件夾加載)解析成不一樣的文件名,因此沒法保證 require('foo') 老是返回同一個對象。若是解析成不一樣的文件,則返回的是不一樣的對象。

The module Object(module 對象)

    ● {對象類型}
    在每一個模塊中,變量 module 是指向表明當前模塊的對象的引用。爲了更方便,module.exports 也能夠經過模塊的全局變量 exports 得到。module 不是真正的全局變量而是每一個模塊都有的局部變量。

module.exports

    ● {對象類型}
    module.exports 對象被模塊系統建立。有時候這是沒法接受的;不少人想把他們的模塊做爲某些類的實例。爲了這個目的須要將但願獲得的對象指派給 module.exports。注意將但願獲得的對象指派給 exports 將只是簡單地改變局部變量 exports 的引用,這極可能不是你想要的實現的。
    例如假設咱們建立一個名叫 a.js 的模塊
1 var EventEmitter = require('events').EventEmitter;
2 
3 module.exports = new EventEmitter();
4 
5 // Do some work, and after some time emit
6 // the 'ready' event from the module itself.
7 setTimeout(function() {
8   module.exports.emit('ready');
9 }, 1000);

    而後在另一個文件咱們能夠
spa

1 var a = require('./a');
2 a.on('ready', function() {
3   console.log('module a is ready');
4 });

   注意給 module.exports 賦值當即完成,不能在回調函數中完成。下面的方式不會正常工做:
    x.js:

1 setTimeout(function() {
2   module.exports = {a: "hello" };
3 }, 0);

   y.js

1 var x = require('./x');
2 console.log(x.a);

 exports alias(exports 別名)

    exports 變量在模塊內都是可用的,並被初始化成指向 module.exports 的引用。如同任何其餘的變量,若是你賦一個新值給它,它將再也不指向以前的值。
    爲了闡述這種行爲,假設 require() 是這樣實現的:
 1 function require(...) {
 2   // ...
 3   function(module, exports) {
 4     // Your module code here
 5     exports = some_func;             // re-assigns exports, exports is no longer
 6                                                  // a shortcut, and nothing is exported.
 7     module.exports = some_func;  // makes your module exports 0
 8   } (module, module.exports);
 9   return module;
10 }

    做爲參考,若是 exportsmodule.exports 之間的關係對你來講想變魔術同樣,忽略 exports 只使用 module.exports

module.require(id)

    ● id 字符串
    ● 返回值: 對象 解析出來的模塊的 module.exports
    module.require 方法提供一個加載模塊的途徑使得就好像 require() 是在原始的模塊被調用的同樣。
    注意爲了這樣作,你必須獲得一個 module 對象的引用。因爲 require() 返回 module.exports,且 module 一般只能在特定的模塊代碼裏被訪問,所以爲了使用它必須明確地導出。

module.id

     ● 字符串
   模塊的模塊標識。典型地這是絕對路徑文件名。

module.filename

    ● 字符串
   模塊的絕對路徑名。

module.loaded

    ● 字符串
   模塊是已經完成加載,仍是還在加載中。

module.parent

    ● 模塊對象
   引用本模塊的模塊。

module.children

    ● 數組
   本模塊引用的模塊。

 All Together...(綜述...)

    要得到 require() 調用時加載的準確文件名,使用 require.resolve() 函數。
    綜上所述,下面是 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, direname(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.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
   b. DIR = path join(PARTS[0 .. I] + "node_modules")
   c. let I = I - 1
6. return DIRS

 Loading from the global folders(從全局文件夾加載)

    若是 NODE_PATH 環境變量設置成以冒號分隔的絕對路徑列表,那麼若是在其餘地方都找不到的話, node 將會從那些路徑下查找模塊。(注意:在 Windows 下,NODE_PATH 被分號分隔而不是冒號。)
    另外,node 會查找一下的位置:
  • 1: $HOME/.node_modules
  • 2: $HOME/.node_libraries
  • 3: $PREFIX/lib/node
    $HOME 是用戶的主目錄,$PREFIX 是 node 配置的 node_prefix
    這些主要是由於歷史的緣由。強烈推薦你將你的依賴放置在本地 node_modules 文件夾。它們會被加載得更快,更可靠。

Accessing the main module(訪問主模塊)

   當一個文件直接從 Node 啓動,require.main 會設置成它的 module。那意味着你能夠以下方式判斷一個文件是否被直接運行
1 require.main === module

    對應文件 foo.js,若是經過 node foo.js 運行該值將是 true,但若是經過 require('./foo') 運行給支將是 false
    由於 module 提供了一個 filename 屬性(一般和 __filename相等),當前應用程序的入口點能夠經過檢查 require.main.filename 獲得。

Addenda: Package Manager Tips(附錄:包管理技巧)

    Node 的 require() 函數的語義被設計得足夠通用化來支持各類常規的目錄結構。例如 dpkgrpmnpm 這些包管理程序將頗有可能發現不需修改就能夠從 Node 模塊建立本地的包。
    下面咱們給出一個可行的推薦目錄結構:
    假設咱們想在 /usr/lib/node/<some-package>/<some-version> 有一個文件夾放置一個包的特定版本的內容。
    包可能依賴其它包。爲了安裝包 foo,你可能須要安裝包 bar 的一個特定的版本。包 bar 可能本身也有依賴,在某些狀況下,這些依賴設置可能衝突或造成循環。
    因爲 Node 查找任何模塊加載時的真實路徑( 也就是說,解析成符號連接),而後查找它們在 node_modules 文件夾中如上所述的依賴,這種狀況對使用以下結構解析很是簡單:
  • /usr/lib/node/foo/1.2.3/ —— foo 包1.2.3版本的內容。
  • /usr/lib/node/bar/4.3.2/ —— foo 包依賴的 bar 包內容。
  • /usr/lib/node/foo/1.2.3/node_modules/bar —— 指向 /usr/lib/node/bar/4.3.2 的符號連接。
  • /usr/lib/node/bar/4.3.2/node_modules/* —— bar 包依賴的包的符號連接。
    所以,即便遇到循環依賴,或依賴衝突,每一個模塊都能獲得它所依賴且可用的版本。
    當 foo 包中的代碼 require('bar'),它將獲得符號連接指向 /usr/lib/node/foo/1.2.3/node_modules/bar 的版本。而後,當 bar 包中的代碼調用 requier('quux'),它將獲得符號鏈接指向 /usr/lib/node/bar/4.3.2/node_modules/quux 的版本。
    此外,爲了使模塊搜索過程更加理想,咱們能夠將包放到 /usr/lib/node_modules/<name>/<version> 而不是直接放到 /usr/lib/node。那麼 node 將不會煩惱於查找 /usr/node_modules/node_modules 中不存在的依賴。
    爲了使模塊能被 node REPL 使用,把 /usr/lib/node_modules 文件夾也添加到 $NODE_PATH 環境變量也頗有用。由於模塊查找使用的 node_modules 文件夾都是相對,且基於調用 require() 的文件的真實路徑,包自己可能在任何地方。
相關文章
相關標籤/搜索