在node.js中,模塊使用CommonJS規范,一個文件是一個模塊
node.js中的模塊可分為三類
node.js提供了大量的模塊供我們使用,比如 想解析一個文件的路徑,可以使用path模塊下的相應方法實現:
const path = require('path');//返回目標文件的絕對路徑console.log(path.resolve('./1.txt'));
運行結果:
/Users/cuiyue/workspace/test/1.txt
使用require引入相應的模塊,即可使用。
__dirname和__filename
node.js的每個模塊都有這兩個參數,它們都是一個絕對路徑的地址,區別是__filename存放了從根目錄到當前文件名的路徑,__dirname只存放從根目錄到模塊的所在目錄:
console.log(__dirname);console.log(__filename);
運行結果:
/Users/cuiyue/workspace/test
/Users/cuiyue/workspace/test/module.js
vm模塊
vm模塊是node.js提供在V8虛擬機中編譯和運行的工具,node.js中的模塊內部實現就是通過此模塊完成。
說說vm的基本用法。
在js環境中有一個eval函數,它可以運行js的代碼字符串,比如:
eval('console.log("Hello javascript.")'); //輸出Hello javascript.
可以看到,eval函數的參數是一段字符串,它可以運行字符串形式的js代碼,但它可以使用上下文環境中的變量:
var num=100;eval('console.log(num)'); //輸出100
以上是可以正確訪問num的值。
vm模塊提供了方法創建一個安全的沙箱,在指定的上下文環境中運行代碼,不受外界干擾。
const vm = require('vm');var num = 100;vm.runInThisContext('console.log(num)');
運行結果:
console.log(num)
^
ReferenceError: num is not defined
可以看到代碼報錯了,說明在vm創建了指定的上下文環境中,拿不到外界的參量。
CommonJS規范
在以前,由于javascript的歷史原因導致它的模塊機制很差,由于這些缺點使得javascript不太善于開發大型應用,于是提出了CommonJS規范以彌補javascript的不足。
CommonJS規范主要分為三塊內容:模塊導入導出、模塊定義、模塊標識。
模塊導入導出
CommonJS中使用require()函數進行模塊的引入。
const mymodule = require('mymodule');
使用exports導出模塊
module.exports = { name: 'Tom'};
引用的名稱可以不帶路徑,若不帶路徑表示引入的是node提供的模塊或是npm安裝的第三方模塊(node_modules)
模塊定義
module對象:在每一個模塊中,module對象代表該模塊自身。
export屬性:module對象的一個屬性,它向外提供接口。
模塊標識
模塊標識指的是傳遞給require方法的參數,必須是符合小駝峰命名的字符串,或者以 .、..、開頭的相對路徑,或者絕對路徑。
node中模塊解析流程
以上為大致流程,下面嘗試著寫一下模塊。
代碼的基本結構:
/** * Module類,用于處理模塊加載 */function Module() {}//模塊的緩存Module._cacheModule = {};//不同擴展名的加載策略Module._extensions = {};//根據moduleId解析絕對路徑,Module._resolveFileName = function(moduleId) {};//入口函數function req(moduleId) {}
附上全部代碼:
const path = require('path');const fs = require('fs');const vm = require('vm');/** * Module類,用于處理模塊加載 */function Module(file) { this.id = file; //當前模塊的id,它使用完整的絕對路徑標識,因此是唯一的 this.exports = {}; //導出 this.loaded = false; //模塊是否已加載完畢}//模塊的緩存Module._cacheModule = {};Module._wrapper = ['(function(exports,require,module,__dirname,__filename){', '});'];//不同擴展名的加載策略Module._extensions = { '.js': function(currentModule) { let js = fs.readFileSync(currentModule.id, 'utf8'); //讀取出js文件內容 let fn = Module._wrapper[0] + js + Module._wrapper[1]; vm.runInThisContext(fn).call( currentModule.exports, currentModule.exports, req, currentModule, path.dirname(currentModule.id), currentModule.id); return currentModule.exports; }, '.json': function(currentModule) { let json = fs.readFileSync(currentModule.id, 'utf8'); return JSON.parse(json); //轉換為JSON對象返回 }, '.node': ''};//加載模塊(實例方法)Module.prototype.load = function(file) { let extname = path.extname(file); //獲取后綴名 return Module._extensions[extname](this);};//根據moduleId解析絕對路徑,Module._resolveFileName = function(moduleId) { let p = path.resolve(moduleId); if (!path.extname(moduleId)) { //傳入的模塊沒有后綴 let arr = Object.keys(Module._extensions); //循環讀取不同擴展名的文件 for (var i = 0; i < arr.length; i++) { let file = p + arr[i]; //拼接上后綴名成為一個完整的路徑 try { fs.accessSync(file); return file; //若此文件存在返回它 } catch (e) { console.log(e); } } } else { return p; }};function req(moduleId) { let file = Module._resolveFileName(moduleId); if (Module._cacheModule[file]) { //若緩存中存在此模塊 return Module._cacheModule[file]; } else { let module = new Module(file); module.exports = module.load(file); return module.exports; }}console.log(req('./a.js')());
a.js的文件內容:
module.exports = function() { console.log('This message from a.js'); console.log(__dirname); console.log(__filename);}
最終運行結果:
This message from a.js
/Users/cuiyue/workspace/test
/Users/cuiyue/workspace/test/a.js
重要代碼說明
_resolveFileName
_resolveFileName方法的主要作用是把傳入的模塊解析成絕對路徑,這樣才可以進行下一步,根據完整的路徑加載模塊。
因此要進行判斷,如果傳入的模塊不存在,則要報錯;如果傳入的模塊已經有擴展名了,就不要拼接了;若沒有擴展名,依次以.js .json .node的順序拼接成完成的模塊進行加載。
_extensions
此對象中封裝了加載不同類型模塊的處理方法,其中若是.json類型則使用fs讀取文件直接轉換成JSON對象并返回。
若是.js文件則讀取后,拼接閉包,將exports,require,module,__dirname,__filename五大參數拼接好,使用vm模塊的沙箱機制運行,得到的結果放入module.exports返回。
總結
以上就是node.js的模塊加載的簡單邏輯,實際上node.js的源碼遠遠比上面的代碼復雜,光是處理模塊路徑、判斷合法等操作就寫了N行。而且我這里沒有寫緩存以及其它的復雜邏輯,但核心差不多就是這些,核心的核心就是用fs.readFileSync讀取js文件,把內容拼接到一個大大的閉包中,這也解釋了為什么我們自己寫的所有node模塊中都會有require方法,exports導出,以及__dirname和__filename參數。
了解了node.js的模塊加載邏輯,在以后寫node.js就更可避免一些誤解,寫出精細的代碼。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。
新聞熱點
疑難解答