定义
commonjs
规范定义了几个点:
- 1、如何声明一个模块,
node中
的一个文件就是一个模块 - 2、每个模块都需要导出最终的结果
module.exports
- 3、每个模块引用其他的模块的时候,需要使用require 语法
其实就是把文件读取出来之后加一个函数执行,最终返回的是 module.exports
module.exports
和 exports
对象指定了同一个空间,但是exports 指向改变了 不会
导致 module.exports 更改。
//一个空的js文件编译的结果是
(function(){
module.exports = exports = {};
return module.exports;
})()
// 如果写成下面的
exports.name = 'romin'
// 导出的结果就是
(function(){
module.exports = exports = {name:'romin'};
return module.exports;
})()
复制代码
原理
大概原理就是把引用的文件读取出来,然后执行,但是读取出来的内容肯定都是字符串,怎么才能让字符串执行呢?这时候,可能会想到 eval 。
1、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 不靠谱
2、new Function
let fn = new Function(`let name = 'romain' \r\n return name`);
let n = fn();// romin
复制代码
node 中也不是这么实现的
3、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 主要的几个步骤
- 1、每个模块都有一个 require 方法: Module.prototype.require
- 2、Module.load -->模块的加载
- 3、Module._resolveFilename --> 解析出文件的绝对路径
- 4、Module.__cache --> 模块的缓存,如果这个模块加载过就直接返回 module.exoprts
- 5、产生模块---> new Module 模块上有两个重要的属性,一个是id,另一个是 exports 对象
- 6、将模块放入缓存中
- 7、尝试加载模块,require('./xx.js') require('.json') require('.node')
- 8、Module._extentions 根据文件的后缀名做对应的处理,读取文件
- 9、 Module.wrap 把内容包裹起来
- 10、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.exports
与 exports
的区别
有人会经常问到 这两者的区别: 看下面的例子
// 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
的赋值就没有了意义。
// 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'
复制代码