模塊是 Node.js 應用程式的基本組成部分,文件和模塊是一一對應的。一個 Node.js 文件就是一個模塊,這個文件可能是 JavaScript 代碼、JSON 或者編譯過的 C/C++ 擴展。 由於JavaScript沒有模塊系統,所以Node.js依靠CommonJS規範自身實現了模塊系統。 ...
模塊是 Node.js 應用程式的基本組成部分,文件和模塊是一一對應的。一個 Node.js 文件就是一個模塊,這個文件可能是 JavaScript 代碼、JSON 或者編譯過的 C/C++ 擴展。
由於JavaScript沒有模塊系統,所以Node.js依靠CommonJS規範自身實現了模塊系統。
模塊的簡單使用——exports 、require 和 module
在編寫和使用每個模塊時,Node.js都有require、exports、module三個預先定義好的變數可供使用。
exports
exports
對象是當前模塊的導出對象,用於導出模塊公有方法和屬性。
事實上,exports 本身僅僅是一個普通的空對象,即 {},它專門用來聲明介面。例如://module.js exports.sayHello = function(name) { console.log('Hello ' + name); };
require
require
函數用於在當前模塊中載入和使用別的模塊,傳入一個模塊名,返回一個模塊導出對象。模塊名可使用相對路徑(以./開頭),或者是絕對路徑(以/或C:之類的盤符開頭)。另外,模塊名中的.js擴展名可以省略。例如://index.js var myModule = require('./module'); myModule.sayHello("node");
module
通過module對象可以訪問到當前模塊的一些相關信息,但最多的用途是覆蓋 exports。例如模塊導出對象預設是一個普通對象,如果想改成一個函數的話:
//module.js module.exports = function(name) { console.log('Hello ' + name); }; //index.js var sayHello = require('./module'); sayHello("node");
模塊進階——模塊載入策略
Node.js的模塊分為兩類,一類為原生(核心)模塊,一類為文件模塊。原生模塊在Node.js源代碼編譯的時候編譯進了二進位執行文件,載入的速度最快。另一類文件模塊是動態載入的,載入速度比原生模塊慢。
內部實現機制
載入文件模塊的工作,主要由原生模塊module來實現和完成,該原生模塊在啟動時已經被載入,進程直接調用到runMain靜態方法。
Module源碼:
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
模塊解析流程:
命令行執行主模塊
命令行執行主模塊
// bootstrap main module. Module.runMain = function() { // Load the main module--the command line argument. Module._load(process.argv[1], null, true); // Handle any nextTicks added in the first tick of the program process._tickCallback(); };
處理模塊
Module.runMain方法會在最後執行_load靜態方法,該方法又會在分析文件名之後執行:
//實例化Module函數 var module = new Module(id, parent);
並根據文件路徑緩存當前模塊對象,該模塊實例對象則根據文件名載入。
module.load(filename);
這時,Node.js會根據不同文件模塊類型的尾碼名來決定載入方法。
- js:通過fs模塊同步讀取js文件並編譯執行。
- node:通過C/C++進行編寫的Addon。通過dlopen方法進行載入。
- json:讀取文件,調用JSON.parse解析載入。
輸出結果
最後js文件形式的模塊會變成以下形式的內容:
(function (exports, require, module, __filename, __dirname) { var circle = require('./circle.js'); console.log('The area of a circle of radius 4 is ' +circle.area(4)); });
所以此時,主模塊內可以使用exports, require, module等變數了,而其他模塊又會通過require引進主模塊,require方法會同runMain一樣調用_load靜態方法,以此類推。
require源碼:
// 傳入模塊路徑作為參數. 返回 模塊的exports屬性. Module.prototype.require = function(path) { assert(path, 'missing path'); assert(typeof path === 'string', 'path must be a string'); return Module._load(path, this, /* isMain */ false); };
Q&A
console.log(this)在瀏覽器和Node中分別列印出什麼?
答:顯然瀏覽器中直接列印this指向Window對象,而在Node中,我們編寫的文件其實外面都包裹了一層函數,而且該函數執行時強制apply將this指向了module.exports,因此此處列印為{}。
為什麼require、__filename、__dirname、module、exports等幾個變數並沒有定義在app.js 文件中,但是這個方法卻存在的原因。
答:這裡提到的所有屬性均是_load方法中我們編寫的js文件外層包裹函數提供給我們的,因此可以直接調用。