commonjs
規範定義了幾個點:javascript
node中
的一個文件就是一個模塊module.exports
其實就是把文件讀取出來以後加一個函數執行,最終返回的是 module.exports
java
module.exports
和 exports
對象指定了同一個空間,可是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
let fn = new Function(`let name = 'romain' \r\n return name`);
let n = fn();// romin
複製代碼
node 中也不是這麼實現的app
終極武器- -vm
,至關於一個虛擬機,它的 runInThisContext 可讓咱們的代碼在一個乾淨的上下文中執行。函數
let vm = require('vm');
let fn = vm.runInThisContext('content//須要執行的代碼')
// 下面就是fn
function (exports,require,module,__filename,__dirname){
/*content*/
}
複製代碼
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
複製代碼
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'
複製代碼
module.exports
與 exports
的區別有人會常常問到 這二者的區別: 看下面的例子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'
複製代碼
exports
和 module.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'
複製代碼