Commonjs規範

Gu_Yan發表於2019-02-22

為什麼有模組化

  • 1.方便程式碼維護
  • 2.每個功能放到一個模組內
  • 3.解決命名問題,全域性變數汙染問題

常見的模組化

  • 1.我們寫方法寫屬性都放在物件裡(單例模式)
    • 缺陷宣告的物件也有可能命名衝突,不能完全解決上述問題
var obj = {
    a:1,
    init(){
        
    }
    fn(){
        
    }
}
複製程式碼
  • 2.自執行函式(IIFE) 實現模組化的功能,要把最終的結果對外暴露
    • 好處
      • 1.沒有名字
      • 2.有自己單獨的執行作用域
      • 3.可以對外暴露一些屬性
    • 缺陷
      • 瀏覽器的檔案載入(http請求),非同步的問題不易解決
(function(){
    ...;
    return XXX
})()
複製程式碼

commonjs規範定義了幾個點(同步的)

  • 1.如何宣告一個模組,node中一個檔案就是一個模組
  • 2.每個模組都需要匯出最終的結果module.exports
  • 3.每個模組使用其他模組的時候需要使用require方法

commonjs的實現流程

就是把檔案讀取出來之後加一個 函式 執行 最終返回的是module.exports

  • 1.每個模組都有一個require方法-->Module.prototype.require
  • 2.呼叫Module._load()載入模組
  • 3.呼叫Module._resolveFilename()解析出檔案的絕對路徑,並且增加擴充名
  • 4.通過Module._cache[filename]檢查是否存在快取,如果有直接返回快取模組的module.exports
  • 5.檢查是否是內建模組(例如fs模組),如果不是,通過new Module(filename)建立一個模組,模組上有兩個重要的屬性id&&exports = {}
  • 6.將建立的模組放到快取中Module._cache[filenama] = module
  • 7.嘗試載入模組tryModuleLoad(module, filename)核心
    • 1).通過path.extname(filename)獲取檔案的副檔名extension
    • 2).通過副檔名extensionModule._extensions中找相應的方法讀取檔案content
    • 3).通過Module.wrap(content)方法將讀取到的內容進行包裝,返回包裝後的結果wrapper
    • 4).通過vm.runInThisContext(wrapper)返回一個可執行函式compiledWrapper
    • 5).compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname)執行
let path = require('path');
let fs = require('fs');
let vm = require('vm');

function req(pathname) {
  return Module._load(pathname);
}

function tryModuleLoad(module, filename) {
  return module.load(filename);
}

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

Module.prototype.load = function (filename) {
  let extension = path.extname(filename);
  Module._extensions[extension](this);
  return this.exports;
}

Module._extensions = {
  '.js': function (module) {
    let content = fs.readFileSync(module.id, 'utf8');
    let wrapper = Module.wrap(content);
    let compiledWrapper = vm.runInThisContext(wrapper);
    let result = compiledWrapper.call(module.exports, module.exports, req, module)
    Module._cache[module.id] = result;
  },
  '.json': function (module) {
    let result = JSON.parse(fs.readFileSync(module.id, 'utf8'));
    Module._cache[module.id] = result;
    module.exports = result;
  }
}

Module._cache = {};

Module._load = function (pathname) {
  let filename = Module._resolveFilename(pathname);
  let cacheModule = Module._cache[filename];
  if (cacheModule) return cacheModule.exports;
  /* 此處略掉檢查內部模組 */
  let module = new Module(filename);
  return tryModuleLoad(module, filename);
}

Module._resolveFilename = function (pathname) {
  let absName = path.resolve(__dirname, pathname);
  try {
    fs.accessSync(absName);
  } catch (err) {
    let extArr = ['.js', '.json'];
    let ext = path.extname(absName);
    if (!ext || !extArr.includes(ext)) {
      extArr.every((extname, index) => {
        let spliceName = absName + extname;
        try {
          fs.accessSync(spliceName);
          absName = spliceName;
          return false;
        } catch (err) {
          if (index == 1) {
            throw new Error(`${pathname} is not defined`)
          }
          return true
        }
      })
    } else {
      throw new Error(`${pathname} is not defined`)
    }
  }
  return absName
}

Module.wrap = function (content) {
  return Module.wrapper[0] + content + Module.wrapper[1];
}

Module.wrapper = ['(function (exports, require, module, __filename, __dirname) {', '});']

let r = req('./user');
console.log(r);

複製程式碼

相關文章