理解require原理和實現一個require

require 內部流程

咱們先大體瞭解一下 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.exportsthis

::: tip 咱們再來看下源碼中對如下幾個疑惑點是怎麼進行處理的 :::spa

一、Module

function Module(id) {
  this.id = id;
  this.exports = {};
  // ..
}
複製代碼

在源碼中咱們發現 Module 上掛載不少,可是咱們真正須要接觸到的分別是 idexports,他們分別是絕對路徑,和默認導出的空對象

二、取得絕對路徑的後綴名,根據後綴名(.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) 是不想有原型上的屬性

三、經過fs對文件進行讀取

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` 上
};
複製代碼

四、對讀到的 .js 文件的內容外層包裹一個函數,而且將字符串轉成函數執行

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)
複製代碼

mini 版require的實現

咱們如今根據步驟結合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);
複製代碼

二、取得絕對路徑的後綴名,根據後綴名(.js)調用對應的處理函數

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

源碼

點擊查看源碼

最後

文章可能有不足的地方,請你們見諒,若是有什麼疑問能夠下方留言討論。

相關文章
相關標籤/搜索