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 会破坏该引用。