node.js中module.exports vs export
如果你有过 Node.js
的开发经验,你可能熟悉用于将代码从一个模块导出到另一个模块的module.exports
和exports
关键字。虽然它们乍一看似乎可以互换使用,但有充分的理由选择其中之一。
导入导出模式
让我们先来看一下 Node.js
中几种常见的 CommonJS
导入导出模式。
修改 module.exports
属性
// math.js
const add = (a, b) => {
return a + b;
};
const subtract = (a, b) => {
return a - b;
};
module.exports.add = add;
module.exports.subtract = subtract;
//index.js
const math = require("./math");
console.log(math.add(2, 3)); // 5
console.log(math.subtract(2, 3)); // -1
在第一种模式中,函数附加到module.exports
对象的属性上。我们可以在 index.js
中看到正确的值被记录。
修改 exports 属性
// math.js
const add = (a, b) => {
return a + b;
};
const subtract = (a, b) => {
return a - b;
};
exports.add = add;
exports.subtract = subtract;
//index.js
const math = require("./math");
console.log(math.add(2, 3)); // 5
console.log(math.subtract(2, 3)); // -1
在第二种模式中,你将函数附加到exports
对象的属性上。在 index.js
中仍然记录了正确的值。
现在,这引出了一个问题:当exports
看起来可以用更少的按键来实现相同的结果时,为什么要使用 module.exports
呢?为了回答这个问题,让我们看一下以下两种模式。
给 module.exports
分配新对象
// math.js
const add = (a, b) => {
return a + b;
};
const subtract = (a, b) => {
return a - b;
};
module.exports = {
add,
subtract,
};
//index.js
const math = require("./math");
console.log(math.add(2, 3)); // 5
console.log(math.subtract(2, 3)); // -1
在第三种模式中,将一个新对象分配给 module.exports
。然后在index.js
中记录了正确的值。
给 exports 分配新对象
// math.js
const add = (a, b) => {
return a + b;
};
const subtract = (a, b) => {
return a - b;
};
exports = {
add,
subtract,
};
//index.js
const math = require("./math");
console.log(math.add(2, 3)); // TypeError: math.add is not a function
console.log(math.subtract(2, 3)); // TypeError: math.subtract is not a function
console.log(math); // {}
在第四种模式中,将一个新对象分配给 exports
。然而,这种模式似乎不起作用,因为 math
看起来是一个空对象。让我们了解一下为什么。
JavaScript 中的对象引用
让我们重新思考一下 JavaScript 中的对象引用是如何工作的。当你将一个对象分配给另一个对象时,两个对象都指向同一个内存地址。修改一个对象也会修改另一个对象。让我们看看这个例子。
const superhero1 = {
name: "Bruce Wayne",
};
const superhero2 = superhero1; // 创建对 superhero1 的引用
superhero2.name = "Clark Kent"; // 修改 superhero2 的名字
console.log(superhero1); // { name: 'Clark Kent' } superhero1 的名字被更新了
在这个例子中,superhero1
和superhero2
都引用同一个superhero
。修改 superhero2
也会修改superhero1
。
然而,分配一个新对象会破坏引用。
const superhero1 = {
name: "Bruce Wayne",
};
let superhero2 = superhero1; // 创建对 superhero1 的引用
superhero2 = { name: "Clark Kent" }; // 分配会破坏引用
superhero2.name = "Barry Allen"; // 只有 superhero2 被修改
console.log(superhero1); // { name: 'Bruce Wayne' } superhero1 没有受到影响
在这种情况下,分配会破坏引用,修改 superhero2
不再影响 superHero1
。
module.exports vs. exports
现在我们了解了 JavaScript
中对象的工作原理,让我们将其与 module.exports
和 exports
相关联起来。在Node.js
中,module
是一个普通的 JavaScript
对象,具有一个exports
属性。exports
是一个普通的JavaScript
变量,恰好被设置为module.exports
。当你在另一个文件中 require
一个模块时,该模块内的代码被执行,并且只返回 module.exports
。
var module = { exports: {} };
var exports = module.exports;
// 通过对象引用将 exports 分配给 module.exports...
exports.add = add; // ...导致 module.exports.add = add
exports.subtract = subtract; // ...导致 module.exports.subtract = subtract
// module.exports 对象包含 add 和 subtract 属性
return module.exports;
然而,如果你将一个新对象分配给exports
,引用将被破坏,更新exports
将不再更新 module.exports
。
var module = { exports: {} };
var exports = module.exports;
// 以下分配会破坏对象引用
exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
};
return module.exports; // module.exports = {}
如果你尝试访问导出对象上的add
或 subtract
,将会抛出错误,因为 module.exports
是空的。因此,虽然在第一个导入导出模式中 module.exports
和 exports
看起来可以互换使用,但它们并不相同。
结论
什么时候应该选择 exports
而不是 module.exports
?简短的答案是你可能不应该这样做。虽然 exports
可能更短,看起来更方便,但它可能会引起混淆,这是不值得的。请记住,exports
只是对 module.exports
的引用,将一个新对象分配给 exports
会破坏该引用。