前言
这篇文章讲了commonJS中两个不太容易理解的问题,先是从概念的角度去谈,然后看用es5实现的commonJS。相信看完后,会收获不小的
exports和module.exports的区别
起初export和module.exports是指向同一个对象,作用是导出模块内部的变量。
用法:
//a.js
exports.name = 'zenos';
module.exports.age = '18';
//index.js
const {name, age} = require('./a.js');
console.log(name,age);
//zenos 18
在exports或者module.exports上面挂载要导出的属性,效果是一致的。但如果是下面这种用法就有点不一样了。
//a.js
exports = {
name: 'zenos'
}
//b.js
module.exports = {
age : '18'
}
//index.js
const Obja = require('./a.js');
const Objb = require('./b.js');
console.log(Obja);
// {}
console.log(Objb);
//{ age : '18'}
可以看到,如果用对象赋值的形式,只有module.exports会生效,而exports不会生效。这是为什么呢?
这是因为我们用require导入的时候,就是导入module.exports,如果像a.js的用法,就会导致exports变量指向新的对象,而不是原来的module.exports。所以就会出现上面看到的情况。
循环依赖
" 在commonJS中,循环依赖会导致死循环吗? ",这是我之前经常问自己的问题。
现在搞明白了,不会死循环。
//a.js
const Objb = require('./b.js');
console.log(Objb);
//b.js
require('./a.js');
exports.name = 'b';
node b.js
首先我们要明白,每次require一个模块,node都会先去一个对象(可以把它叫做module_cache)里面找,如果找到了,就直接返回。如果没有找到,就去装载。
装载的过程:先在cache对象里放module.exports对象,现在这个对象是空的。然后找到该文件,编译该文件,执行该文件。之后,如果文件里面有在module.exports对象上面挂载东西,那其他模块就能通过require拿到。
注意这个顺序很重要,是首先在生成一个空的module.exports对象,然后再去执行文件的代码
文件b.js执行的过程:
- 编译执行
b.js - 遇到了
require(' ./a.js') - 在cache对象里面找属于
a.js的module.exports对象。没有找到,装载a.js。 - 在装载
a.js过程,遇到了require('./b.js') - 在cache对象里面找属于
a.js的module.exports对象,找到了。
虽然找到了,但是一个空对象,因为b.js现在还没有在上面挂载东西
- 执行
console.log(Objb),
可以预见打印结果为
{}
- 装载
a.js完毕,返回属于它的module.exports对象(也是空的) - 执行
exports.name = 'b',在上面挂了一个name b.js执行完毕
简单吧,哈哈😄
__filename和__dirname的区别
__filename:返回该文件的绝对路径;
__dirname:返回该文件所处目录的绝对路径;
现在有个a.js文件,其目录为/user/project/a.js
//a.js
console.log(__filename); // /user/project/a.js
console.log(__dirname); // /user/project
如何查看用es5对commonJS的实现
理解一个规则的最好办法就是看下它是如何实现的。这里我们需要借用webpack,webpack可以将commonJS的语法转成用es5表示
初始化npm项目,安装webpack依赖
npm init -y
npm install webpack webpack-cli
打包上面的a.js和b.js
npx webpack b.js
找到dist/bundle.js就可以看见转义之后的代码了
分析代码
下面是上面webpack编译出来的代码。实际的代码的变量名称不太好看明白,所以更改一变量的名称让代码更好懂
(() => {
var webpack_module = {
'./a.js': (module, exports, require) => {
const t = require('./b.js');
console.log(t);
},
'./b.js': (module, exports, require) => {
require('./a.js'), (exports.name = 'b');
},
};
var cache = {};
function require(module_name) {
var n = cache[module_name];
if (void 0 !== n) return n.exports;
var s = (cache[module_name] = { exports: {} });
webpack_module[module_name](s, s.exports, require);
return s.exports;
}
require('./b.js');
})();
解释:
- webpack_module对象是储存模块代码的对象,将模块的路径当作key,value是函数,函数内部就是我们在文件里面编写的代码。函数的参数有module、export、require,这就是我们在文件中调用的导入导出时用到的变量
- cache是用来储存模块导出的exports的对象
- require函数的功能就是装载,装载的过程我上面讲过了,就不重复了。
通过编译后的代码来回看上面的问题
- exports和module.exports的区别
- 循环依赖
是不是有了更深刻的理解了,哈哈 🕶
总结
通过webpack编译后的es5源码,来理解commonJS不太好懂的规则
- exports和module.exports的区别
- 循环依赖