node中的module.exports到底咋實現的?
具體思路:
- 解析出一個絕對路徑來
2)根據絕對路徑查詢檔案,沒有加字尾的,新增字尾查詢檔案
-
查到檔案情況下,根據檔名查內容。
1.先去快取中看一下這個檔案是否存在,如果有,則返回快取
2.如果沒有,則執行以下操作:
i.建立一個模組 module ii.根據不同的字尾名,載入模組 iii.載入完成後,將內容存到快取中。
上程式碼:
let fs = require('fs');
let path = require('path');
let vm = require('vm');
function req(moduleId) {
let p = Module._resolveFileName(moduleId);// p是一個絕對路徑
if (Module._cacheModule[p]) { //從快取中取
// 模組不存在,如果有直接把exports物件返回即可
return Module._cacheModule[p].exports;
}
let module = new Module(p); // 表示沒有快取就生成一個模組
// 載入模組
let content = module.load(p); // 載入模組
Module._cacheModule[p] = module;
module.exports = content; //module.exports = {name:'zfpx'};
return module.exports
}
let a = req('./a.js');
複製程式碼
具體其餘模組講解:
1.Module._resolveFileName:用於解析絕對路徑
Module._resolveFileName = function (moduleId) {
let p =path.join(__dirname,moduleId);//絕對路徑
// 沒有字尾我在加上字尾 如果傳過來的有字尾就不用加了
if (!path.extname(moduleId)) {
let arr = Object.keys(Module._extensions);
for (let 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;
}
}
複製程式碼
2.Module._cacheModule是一個物件,用來存載入過的模組
key: 檔名,value: 模組名
Module._cacheModule = {}// 根據的是絕對路徑進行快取的
複製程式碼
3.let module = new Module(p); // 表示沒有快取就生成一個模組 其中p是檔名
function Module(p) {
this.id = p; // 當前模組的標識,Module._cacheModule物件取快取時,根據id判斷快取是否存在
this.exports = {}; // 每個模組都有一個exports屬性
}
複製程式碼
4.let content = module.load(p); // 載入模組 我們引入的檔案可能時.js,.json,.node格式,load方法就是根據不同的字尾名,做不同方式的解析
// 模組載入的方法
Module.prototype.load = function (filepath) {
// 判斷載入的檔案是json還是node或者是js
let ext = path.extname(filepath); // 判斷檔案的字尾名是 .js/.json/.node
let content = Module._extensions[ext](this); // content就是json的內容
return content
}
複製程式碼
5.load方法的核心時Module._extensions,這個方法幾乎是核心內容了它的作用是,獲得檔案內容
// 所有的載入策略
Module.wrapper = ['(function(exports,require,module){','\n})'];
Module._extensions = {
'.js': function (module) {
// 讀取js檔案 增加一個閉包了
let script = fs.readFileSync(module.id,'utf8');
let fn = Module.wrapper[0] + script + Module.wrapper[1];
let ff = vm.runInThisContext(fn);
//在module.exports環境下執行(function(exports,require,module){module.exports='zfpx'})函式,
// 引數分別對應exports-->module.exports(暗含exports=module.exports),
// require-->req,module
ff.call(module.exports,module.exports,req,module);
return module.exports
},
'.json': function (module) {
return JSON.parse(fs.readFileSync(module.id, 'utf8')); // 讀取那個檔案
},
}
複製程式碼
重點來了,解析js方法我怎麼看不懂了。不要急。
let fn = Module.wrapper[0] + script + Module.wrapper[1];這句是把檔案的內容通過閉包包起來。
let ff = vm.runInThisContext(fn);
vm.runInThisContext()方法會建立一個獨立的沙箱環境,以執行對引數fn的編譯,執行並返回結果。 vm.runInThisContext()方法執行的程式碼沒有許可權訪問本地作用域,但是可以訪問Global全域性物件。
ff.call(module.exports,module.exports,req,module);--->ff是:(function(exports,require,module))
傳入引數,執行閉包,這裡解釋下exports和module.exports的關係
傳參對應關係:
exports---->module.exports
require ----> req
module ----> module
可見:
exports 是module.export 的一個別名; module.exports = exports
比如這裡被引入的檔案內容:
this === module.exports (true) //this就是module.exports
module.exports = 'zfpx'; // 'zfpx'
複製程式碼
若將module.exports='zfpx'改為exports='ff';則結果為{}
本來exports = module.exports = {}
後來exports = 'zfpx',切斷了exports
和module.exports的關係,而最後返回的是module.exports,因此結果是{}
若將module.exports='zfpx'改為exports.a = '1',則結果為{'a':1}
本來exports = module.exports = {}
後來exports.a = 'zfpx';
則module.exports = {a:'zfpx'}