CommonJS是一個模塊化的規範,Nodejs的模塊系統,就是參照CommonJS規範實現的。每一個文件就是一個模塊 ,每一個模塊都要本身的做用域。node
首先咱們先了解一下模塊化加載:git
.js
或 .json
的文件.js
文件,就把內容加一個閉包每一個模塊內部,都有一個 module
對象,表明當前模塊。github
查看 console.log(module)
module都有哪些屬性:json
module.id
模塊的識別符,一般是帶有絕對路徑的模塊文件名module.filename
模塊的文件名,帶有絕對路徑module.loaded
返回一個布爾值,表示模塊是否加載module.parent
返回一個對象,表示調用該模塊的模塊module.children
返回一個數組,表示該模塊要用到的其餘模塊module.exports
表示模塊對外輸出的值,默認值爲空對象function Module(id) {
// 下面就簡單定義兩個經常使用的
this.id = id
this.exports = {}
}
複製代碼
// 咱們本身寫的文件模塊 要寫路徑 ./ 或者../
let hello = require('./1.NodeJS')
console.log(hello) // first NodeJS
複製代碼
//操做文件的模塊
let fs = require('fs')
// 處理路徑的模塊
let path = require('path')
// 虛擬機模塊,沙箱運行,防止變量污染
let vm = require('vm')
複製代碼
Module._resolveFilename
方法實現的功能是判斷用戶引入的模塊是否包含後綴名,若是沒有後綴名會根據 Module._extensions
的鍵的順序查找文件,直到找到後綴名對應的文件的絕對路徑,優先查找 .js
數組
// 將引入文件處理爲絕對路徑
Module._resolveFilename = function (p) {
// 以js或者json結尾的
if ((/\.js$|\.json$/).test(p)) {
// __dirname當前文件所在的文件夾的絕對路徑
// path.resolve方法就是幫咱們解析出一個絕對路徑出來
return path.resolve(__dirname, p);
} else {
// 沒有後後綴 自動拼後綴
// Module._extensions 處理不一樣後綴的模塊
let exts = Object.keys(Module._extensions);
let realPath; // 存放真實存在文件的絕對路徑
for (let i = 0; i < exts.length; i++) {
// 依次匹配對應擴展名的絕對路徑
let temp = path.resolve(__dirname, p + exts[i])
try {
// 經過fs的accessSync方法對路徑進行查找,找不到對應文件直接報錯
fs.accessSync(temp)
realPath = temp
break
} catch (e) {
}
}
if (!realPath) {
throw new Error('module not exists');
}
// 將存在絕對路徑返回
return realPath
}
}
複製代碼
Module._extensions
處理對應模塊擴展名。這裏咱們只提 .js
.json
,對應模塊的處理功能咱們在後面來實現緩存
Module._extensions = {
"js": function() {},
"json": function() {}
}
複製代碼
當用戶重複加載一個已經加載過的模塊,咱們只有第一次是加載,而後放入緩存中,後面在加載時,直接返回緩存中便可bash
Module._cacheModule = { }
存放模塊緩存閉包
// 獲取內存中的結果
let cache = Module_cacheModule[filename]
// 判斷是否存在
if (cache) {
// 若是存在直接將 exports對象返回
return cache.exports
}
// 若是不存在內存中,就建立模塊,而後加入到內存中
let module = new Module(filename)
Module._cacheModule[filename] = module
複製代碼
根據前面咱們說到的模塊化,對於已經存放在內存中的咱們直接返回就能夠了,對於新添加的模塊,咱們該讀取文件了, 根據傳入的模塊,嘗試加載模塊方法app
// 根據傳入的模塊,嘗試加載模塊方法
function tryModuleLoad(module) {
// 前面咱們已經提到 module.id 爲模塊的識別符,一般是帶有絕對路徑的模塊文件名
// path.extname 獲取文件的擴展名
/* let ext = path.extname(module.id);
// 若是擴展名是js 調用js處理器 若是是json 調用json處理器
Module._extensions[ext](module); // exports 上就有了數組 */
let ext = path.extname(module.id);//擴展名
// 若是擴展名是js 調用js處理器 若是是json 調用json處理器
Module._extensions[ext](module); // exports 上就有了數組
}
複製代碼
Module._extensions
處理對應後綴名模塊。這裏咱們只提 .js
.json
模塊化
// 處理對應後綴名模塊
Module._extensions = {
".js": function (module) {
// 對於js文件,讀取內容
let content = fs.readFileSync(module.id, 'utf8')
// 給內容添加閉包, 後面實現
let funcStr = Module.wrap(content)
// vm沙箱運行, node內置模塊,前面咱們已經引入, 將咱們js函數執行,將this指向 module.exports
vm.runInThisContext(funcStr).call(module.exports, module.exports, req, module)
},
".json": function (module) {
// 對於json文件的處理就相對簡單了,將讀取出來的字符串轉換未JSON對象就能夠了
module.exports = JSON.parse(fs.readFileSync(module.id, 'utf8'))
}
}
複製代碼
上面咱們使用了 Module.wrap
方法,是幫助咱們添加一個閉包,簡單說就是咱們在外面包了一個函數的前半段和後半段
// 存放閉包字符串
Module.wrapper = [
"(function (exports, require, module, __filename, __dirname) {",
"})"
]
複製代碼
// 將咱們讀到js的內容傳入,組合成閉包字符串
Module.wrap = function (script) {
return Module.wrapper[0] + script + Module.wrapper[1];
}
複製代碼
要運行,咱們來看一下完整的模塊加載代碼
// 模塊加載
Module._load = function (f) {
// 相對路徑,可能這個文件沒有後綴,嘗試加後綴
let fileName = Module._resolveFilename(f); // 獲取到絕對路徑
// 判斷緩存中是否有該模塊
if (Module._cacheModule[fileName]) {
return Module._cacheModule[fileName].exports
}
let module = new Module(fileName); // 沒有就建立模塊
Module._cacheModule[fileName] = module // 並將建立的模塊添加到緩存
// 加載模塊
tryModuleLoad(module)
return module.exports
}
複製代碼
到這裏,一個簡單的module就實現了,讓咱們測試一下吧
// 測試代碼
function req(p) {
return Module._load(p); // 加載模塊
}
// 第一次沒有緩存,建立module,並添加到緩存中
let str = req('./1.NodeJS');
// 第二次就是返回的緩存中的
let str1 = req('./1.NodeJS.js');
console.log(str) // first NodeJS
console.log(str1) // first NodeJS
複製代碼
重要:爲了方便你們瞭解、查看、調試代碼,完整的源碼參見gitHub
本篇文章是基於CommonJS規範,實現了一個簡單的NodeJS模塊化,主要目的在於理解 NodeJS 模塊化的實現思路,但願對你們瞭解模塊化起到必定的做用。
未來的你,必定會感謝如今拼命努力的本身!