两者的关系
exports = module.exports = {}
nodejs如何导出模块
当不改变module.exports和exports的指向,module.exports和exports均可导出,因为上面也说过这两个对象实际上指向的都是同一个堆地址
不改变指向
//a.js
exports.a = 1;
module.exports.b = 2;
//b.js
const test = require('./a');
console.log(test);
//output: { a: 1, b: 2 }
改变指向:仅module.exports可导出
//a.js
exports.a = 1; // exports = { a: 1 };
module.exports = { b: 2 };
//b.js
const test = require('./a');
console.log(test);
//output: { b: 2 }
刨根问底
为什么指向一旦被改变exports就不能导出对象了呢?
首先node加载模块的时候会用一个函数将代码包裹
(function (exports, require, module, __filename, __dirname) {
});
当加载模块的时候,module已经被当作参数传入,所以即使module.exports的值改变也能够在其他文件中进行调用,也就是为什么指向一旦被改变,只有module.exports可导出。
那么有同学问了exports也被当作参数传入啦~~~
利用node-inspector调试我们可以找到答案
Module.prototype._compile = function(content, filename) {
......
let result;
//这里传入的exports其实是this.exports,而当前this是指向module的,所以加载模块传入的exports其实是module.exports,所以一旦改变了exports指向,就不能通过exports导出了
const exports = this.exports;
const thisValue = exports;
const module = this;
if (requireDepth === 0) statCache = new SafeMap();
if (inspectorWrapper) {
result = inspectorWrapper(compiledWrapper, thisValue, exports,
require, module, filename, dirname);
} else {
result = ReflectApply(compiledWrapper, thisValue,
[exports, require, module, filename, dirname]);
}
hasLoadedAnyUserCJSModule = true;
if (requireDepth === 0) statCache = null;
return result;
};
为什么module.exports = {}的多次导出和module.exports.x的多次导出结果不一样?
其实也就是值拷贝的原理
//b.js
module.exports = {
count: 1,
};
setTimeout(() => {
module.exports = {
count: 2,
};
}, 100);
//a.js
const b = require("./b");
console.log(b); //{count: 1}
setTimeout(() => {
console.log(b); //{count: 1}
}, 200);
翻译一下
setTimeout(() => {
myModule.exports = {
count: 2,
};
}, 100);
const myModule = {
exports: {},
};
myModule.exports = {
count: 1,
};
const myObj = myModule.exports;
//const b = require('./b');
//myModule.exports导出的对象直接赋值给了新对象myObj,两个对象指向同一个堆地址
//这时候重新赋值myModule.exports对象(改变了myModule.exports的地址指向),当然不会影响新对象myObj
console.log(myObj); //{count: 1}
setTimeout(() => {
console.log(myObj); //{count: 1}
}, 200);
改变一下b.js
//b.js
module.exports.count = 1;
setTimeout(() => {
module.exports.count = 2;
}, 200);
//a.js
const b = require("./b");
console.log(b.count); //{count: 1}
setTimeout(() => {
console.log(b.count); //{count: 2}
}, 200);
翻译一下
setTimeout(() => {
myModule.exports.count = 2;
}, 100);
const myModule = {
exports: {},
};
myModule.exports.count = 1;
const myObj = myModule.exports;
//const b = require('./b');
//myModule.exports导出的对象直接赋值给了新对象myObj,两个对象指向同一个堆地址
//这时候改变myModule.exports.count对象(两个对象仍然指向同一个堆地址),结果自然会改变
console.log(myObj); //{count: 1}
setTimeout(() => {
console.log(myObj); //{count: 2}
}, 200);
所以为什么我们在导出模块时建议使用myModule.exports = {}导出,以避免预期之外的情况