commonjs規範

定義

commonjs規範定義了幾個點:javascript

  • 一、如何聲明一個模塊,node中的一個文件就是一個模塊
  • 二、每一個模塊都須要導出最終的結果 module.exports
  • 三、每一個模塊引用其餘的模塊的時候,須要使用require 語法

其實就是把文件讀取出來以後加一個函數執行,最終返回的是 module.exportsjava

module.exportsexports 對象指定了同一個空間,可是exports 指向改變了 不會致使 module.exports 更改。node

//一個空的js文件編譯的結果是
(function(){
    module.exports = exports = {};
    return module.exports;
})()

// 若是寫成下面的
exports.name = 'romin'
// 導出的結果就是
(function(){
    module.exports = exports = {name:'romin'};
    return module.exports;
})()
複製代碼

原理

大概原理就是把引用的文件讀取出來,而後執行,可是讀取出來的內容確定都是字符串,怎麼才能讓字符串執行呢?這時候,可能會想到 eval 。json

一、eval 是否可行

eval('console.log('a')')
複製代碼

可是 eval 執行不太乾淨,eval 會從當前的執行上下文中去取相關的變量,舉個🌰:緩存

// a文件
module.exports = name;
// 導出的結果就是,
(function(){
    module.exports = exports = name;
    return module.exports;
})()
// b 文件中引用,讀取到了 a 導出的函數,讀出來的是字符串,
var n = require('./a');
// eval('(function(){
// module.exports = exports = name;
// return module.exports;
//})()')
var name = 'romain';

a 文件中沒有有name 這個變量,執行結果就是錯的,可是在 b 文件中能夠正常是用了。
複製代碼

因此 eval 不靠譜bash

二、new Function

let fn = new Function(`let name = 'romain' \r\n return name`);
let n = fn();// romin
複製代碼

node 中也不是這麼實現的app

三、vm 沙箱 ,nod

終極武器- -vm ,至關於一個虛擬機,它的 runInThisContext 可讓咱們的代碼在一個乾淨的上下文中執行。函數

let vm = require('vm');
let fn = vm.runInThisContext('content//須要執行的代碼')
// 下面就是fn
function (exports,require,module,__filename,__dirname){
    /*content*/
}
複製代碼

3.1 先了解一個 path

let path = require('path');
console.log(path.resolve('my','file'));
// /Users/mac/Documents/JS/node/my/file
console.log(path.join('my','file'))
// my/file
console.log(__dirname)
// /Users/mac/Documents/JS/node
console.log(__filename)
// /Users/mac/Documents/JS/node/test.js
console.log(path.extname(__filename))
// .js
複製代碼

3.2 主要的幾個步驟

  • 一、每一個模塊都有一個 require 方法: Module.prototype.require
  • 二、Module.load -->模塊的加載
  • 三、Module._resolveFilename --> 解析出文件的絕對路徑
  • 四、Module.__cache --> 模塊的緩存,若是這個模塊加載過就直接返回 module.exoprts
  • 五、產生模塊---> new Module 模塊上有兩個重要的屬性,一個是id,另外一個是 exports 對象
  • 六、將模塊放入緩存中
  • 七、嘗試加載模塊,require('./xx.js') require('.json') require('.node')
  • 八、Module._extentions 根據文件的後綴名作對應的處理,讀取文件
  • 九、 Module.wrap 把內容包裹起來
  • 十、vm.runInThisContext()
3.2.1
let path = require('path');
let fs = require('fs');
let vm = require('vm');

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

// 緩存
Module._cache = {}

function req(id){
  // 轉出一個絕對路徑, 若是沒有後綴,須要依次增長 .js,.json,.node 來艙室加載模塊
  let filaname = path.resolve(__dirname,id);
  
  // 若是這個文件能夠訪問,說明這個文件已經存在,若是拋出異常說明該文件不存在,須要增長後綴
  if(!path.extname(filaname)){
    
    let extentionNames = Object.keys(Module._extentions);

    for(let i =0;i<extentionNames.length;i++){
      filaname = `${filaname}.${extentionNames[i]}`
      try{
        fs.accessSync(filaname);
      }catch(e){
        console.log('不存在')
      }
    }
  }
  
  // 緩存根據的是絕對路徑,若是有緩存就直接返回出緩存的
  if( Module._cache[filaname]){
    return  Module._cache[filaname].exports;
  }

  let module = new Module(filaname)
  Module._cache[filaname] = module;
  
  // 嘗試 加載模塊
  tryModuleLoad(module);
  // 默認返回 module.exports 對象
  return module.exports;
}



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

Module._extentions = { };

Module._extentions['.js'] = function(module){
  let content = fs.readFileSync(module.id,'utf8');
  // 構建了一個匿名的函數, 可是它如今仍是一個字符串
  content = Module.wrapper[0]+content+Module.wrapper[1];
  let fn = vm.runInThisContext(content);
  // function (exports,require,module,__filename,__dirname){let str = 'romin'
      // module.exports = str;
  // }
  fn.call(module.exports,exports,req,module)
};

Module._extentions['.json'] = function(module){
  module.exports = JSON.parse(fs.readFileSync(module.id,'utf8'))
};


function tryModuleLoad(module){
  let extentions = path.extname(module.id)||'.js' //獲取文件的擴展名
  Module._extentions[extentions](module)
}

let str = req('./1.js');
console.log(str); //'romin'
複製代碼

4 module.exportsexports 的區別

有人會常常問到 這二者的區別: 看下面的例子ui

// a.js
let name  = 'romin';
exports.name = name;
console.log(module.exports) // { name: '' }
console.log(exports) // { name: '' }
console.log(module.exports === exports)  //true
複製代碼
// b.js
let name = require('./a');
console.log(name); // 'romin'
複製代碼

exportsmodule.exports 都是內置空對象,而默認導出的是 module.exports,若是導出的時候沒有給 module.exports賦值,那麼會自動執行 module.exports = exports;若是說給module.exports進行了賦值,exports 的賦值就沒有了意義。this

// a.js
let name  = 'xxx';
exports.name = name;
module.exports = 'romin'
console.log(module.exports) // 'romin'
console.log(exports) // { name: 'xxx' }
console.log(module.exports === exports)  //false
複製代碼
// b.js
let name = require('./a');
console.log(name); // 'romin'
複製代碼
相關文章
相關標籤/搜索