node.js中module.exports vs exports
如果你有过 Node.js
的开发经验,你可能熟悉用于将代码从一个模块导出到另一个模块的 module.exports
和exports
关键字。虽然它们乍一看似乎可以互换使用,但有充分的理由选择其中之一。
导入导出模式 让我们先来看一下 Node.js
中几种常见的 CommonJS
导入导出模式。
修改 module.exports
属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 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 属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 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
分配新对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 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 分配新对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 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 中的对象引用是如何工作的。当你将一个对象分配给另一个对象时,两个对象都指向同一个内存地址。修改一个对象也会修改另一个对象。让我们看看这个例子。
1 2 3 4 5 6 7 8 9 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
。
然而,分配一个新对象会破坏引用。
1 2 3 4 5 6 7 8 9 10 11 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
。
1 2 3 4 5 6 7 8 9 10 11 12 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
。
1 2 3 4 5 6 7 8 9 10 11 12 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
会破坏该引用。