commonjs模塊的簡易版本實現

實現簡易版本的commonjs加載

帶着下面的問題去讀讀代碼吧!javascript

  • node裏面的文件就是模塊?
  • 讀取的js文件,是如何執行的?
  • exports module.exports的關係?
  • js裏面的對象之間的引用關係?
如何理解文件就是模塊?

node裏面,js代碼在被引入過去都會被包裝成以下的函數,是否是咱們只須要再咱們的require函數裏面去調用這個函數(固然下面的那個函數須要咱們本身讀取到文件內容,而後組裝成下面的函數形式),解析函數字符串爲可執行的js,傳入引用的參數。module.exports就會被賦值。再返回module.exports,就獲得了須要的模塊裏面的內容了前端

// (function(exports,module,require){
    this == module.exports // true
    module.exports = 'hello';
// })
module.exportsexports
exports = module.exports

exports.a = 'xxx' // 一個引用地址,module.exports也會被賦值
exports = 'xxx' // 這個時候exports的引用地址就錯誤了。就獲取不到模塊的內容了
參數的引用

但咱們傳遞給函數的參數是一個引用類型數據的時候。當函數執行的時候,會先執行參數賦值的階段,引用類型數據的話,賦值的就是一個引用地址。就會出現以下的狀況java

var test = {}

function fn(a){
    a.name = 'xxx'
}

fn(test)

console.log(test) // {name:'xxx'}
代碼實現以下
const fs = require("fs");
const path = require("path");
const vm = require("vm");

function Module(filePath) {
  this.id = filePath;
  this.exports = {};
}

Module._cache = {};

Module.fnStr = ["(function(module,exports,req,__fileName,__dirname){\n", "})"];

Module.extensions = {
  ".js": function(module) {
    const content = fs.readFileSync(module.id, "utf8");
    const fnString = Module.fnStr[0] + content + Module.fnStr[1];
    const fn = vm.runInThisContext(fnString);
    // 文件就是模塊 exports = module.exports exports其實就是指向的 module.exports
    // 函數執行後 會給傳遞進去的module.exports賦值。module又是一個對象,函數參數的引用,就給下面實例化的 module上賦值了。就可以獲取到文件的內容了
    fn.call(
      module.exports,
      module,
      module.exports,
      req,
      module.id,
      path.dirname(module.id)
    );
  },
  ".json": function(module) {
    const jsonString = fs.readFileSync(module.id, "utf8");
    module.exports = JSON.parse(jsonString);
  }
};

Module.resolveFileName = function(filePath) {
  let absolutePath = path.resolve(__dirname, filePath);
  let flag = fs.existsSync(absolutePath); // 判斷文件是否存在
  let current = absolutePath;
  if (!flag) {
    let keys = Object.keys(Module.extensions);
    for (let i = 0; i < keys.length; i++) {
      let combinePath = absolutePath + keys[i];
      let flag = fs.existsSync(combinePath); // 增長文件後綴後再判斷文件是否存在
      if (flag) {
        current = combinePath;
        break;
      } else {
        current = null;
      }
    }
  }
  if (!current) {
    throw new Error(`當前文件不存在${current}`);
  }
  return current;
};

Module.prototype.load = function() {
  // this.id 就是文件路徑
  let extName = path.extname(this.id);
  Module.extensions[extName](this);
};

function req(filePath) {
  let absolutePath = Module.resolveFileName(filePath); // 獲取到文件的絕對路徑 支持了 動態匹配.js .json後綴
  let module = new Module(absolutePath);

  if (Module._cache[absolutePath]) {
    return Module._cache[absolutePath].exports;
  }

  // 值是一個引用地址。真正有值的時候是再執行完module.load()後
  Module._cache[absolutePath] = module;
  module.load(); // 讀取文件
  return module.exports;
}
本人前端菜鳥一枚,歡迎閱讀並指出錯誤!
相關文章
相關標籤/搜索