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'
复制代码