咱們先大體瞭解一下 require()引入一個 `.js` 或者 `.json` 文件時候,內部大體是怎麼處理的。 node
let name = require('./a')
console.log(name)
複製代碼
上述代碼引入 a.js
文件時候,內部大體發生的流程以下:git
一、將 ./a
轉化爲絕對路徑,而且補充後綴名(c:\Users\chenying\Desktop\code\a.js
)github
二、根據絕對路徑判斷緩存中是否存在緩存的文件,若是存在則取緩存,不存在則繼續json
三、建立 Module
實例 module
,將絕對路徑傳入緩存
四、取得絕對路徑的後綴名,根據後綴名(.js
)調用對應的處理函數app
五、讀 .js
和 .json
文件思路大同小異,經過fs.readFileSync()讀取文件內容函數
六、對讀到的 .js
文件的內容外層包裹一個函數,而且將字符串轉成函數執行ui
七、對讀到的 .json
文件的內容,轉爲對象,而且賦值給module.exports
this
::: tip 咱們再來看下源碼中對如下幾個疑惑點是怎麼進行處理的 :::spa
Module
類function Module(id) {
this.id = id;
this.exports = {};
// ..
}
複製代碼
在源碼中咱們發現 Module
上掛載不少,可是咱們真正須要接觸到的分別是 id
和 exports
,他們分別是絕對路徑,和默認導出的空對象
.js
)調用對應的處理函數Module._extensions = Object.create(null); //建立一個對象
Module._extensions['.js'] = function(module, filename) {}; // 在對象上掛載 `.js`的處理函數
Module._extensions['.json'] = function(module, filename) {}; // 在對象上掛載 `.json`的處理函數
Module._extensions[extension](this, filename); // 根據絕對路徑的後綴名,調用掛載在對象上相應的方法
複製代碼
用 Object.create(null)
是不想有原型上的屬性
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8'); // 讀取文件內容
module._compile(stripBOM(content), filename); // 對讀取的內容包裹一層函數,而且轉爲函數自執行,讓用戶本身把想要導出的內容掛載到 `module.exports` 上面去
};
Module._extensions['.json'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module.exports = JSON.parse(stripBOM(content)); // 讀到的json文件直接掛載到 `module.exports` 上
};
複製代碼
let wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
const wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
const wrapper = Module.wrap(content);
compiledWrapper = vm.runInThisContext(wrapper)
compiledWrapper.call(this.exports, this.exports, require, this,filename, dirname)
複製代碼
咱們如今根據步驟結合node來實現一款require()加深理解
module
實例,將module.exports導出let path = require('path');
let fs = require('fs');
let vm = require('vm');
function Module(id){
this.id = id;
this.exports = {}
}
function myRequire(filePath){
let absPath = path.resolve(__dirname,filePath); // 把當前的filePath變成一個絕對路徑
let module = new Module(absPath);
module.load(); // 加載模塊
return module.exports
}
let r = myRequire('./a.js');
console.log(r);
複製代碼
Module._extensions = {};
Module.prototype.load = function(){
let ext = path.extname(this.id); // 取出當前實例上掛載的絕對路徑,獲取後綴名
Module._extensions[ext](this) // 根據後綴名調用對應的處理函數
}
複製代碼
Module._extensions
上補充對應的處理函數let wrapper = [
'(function(exports,module,require,__dirname,__filename){'
,
'})'
]
Module._extensions['.js'] = function(module){
let script = fs.readFileSync(module.id,'utf8'); //讀取文件
let functStr = wrapper[0] + script + wrapper[1]; // 字符串包裹
let fn = vm.runInThisContext(functStr); //將字符串轉爲函數
fn.call(module.exports,module.exports,module,myRequire);//執行函數
}
Module._extensions['.json']= function(module){
let script = fs.readFileSync(module.id,'utf8');//讀取文件
module.exports = JSON.parse(script); // 將讀取到的json掛載到 `module.exports`
}
複製代碼
目前已經根據源碼思路來實現一款mini版require(),可是還有相似於實現緩存、自動補全後綴名等功能就不貼代碼了,能夠看下方的源碼,歡迎star
文章可能有不足的地方,請你們見諒,若是有什麼疑問能夠下方留言討論。