module.exports實現原理以及和exports到底是啥關係

蘆夢宇發表於2018-05-30

node中的module.exports到底咋實現的?

具體思路:

  1. 解析出一個絕對路徑來

2)根據絕對路徑查詢檔案,沒有加字尾的,新增字尾查詢檔案

  1. 查到檔案情況下,根據檔名查內容。

    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'}

相關文章