问题描述
有人可以举例说明module.exports = {...}将如何导致意外行为。
我正在读你还不懂js,我在 https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/ch8.md#node-commonjs-modules
一些开发人员习惯于替换默认导出 对象,例如:
// defining a new object for the API module.exports = { // ..exports.. };
这种方法有一些奇怪之处,包括意外情况 如果多个这样的模块循环依赖,则会出现这种情况。如 这样,我建议不要更换对象。如果要分配 使用对象文字样式定义一次导出多个 可以改为:
Object.assign(module.exports,{ // .. exports .. });
这里发生的是用您的{ 指定模块的公共API,然后将Object.assign(..) 在现有属性上执行所有这些属性的浅表复制 module.exports对象,而不是替换它。这是一个不错的平衡 方便性和更安全的模块行为。
解决方法
在模块运行之前为您的模块创建exports
对象,并且如果存在循环依赖关系,则其他模块可能可以访问该默认对象,然后才能将其填充。如果您替换,它们可能具有旧的原始对象,并且(最终)看不到您的导出。如果您将其添加到,则即使该对象最初没有导出,但即使其他模块在这些导出存在之前就可以访问该对象,最终也将具有该导出。
CJS模块文档的Cycles section中的更多内容。
我们可以修改该部分中的循环示例以进行演示:
a.js
(注释更改):
console.log('a starting');
// Removed: `exports.done = false;`
const b = require('./b.js');
console.log('in a,b.done = %j',b.done);
exports = {done: true}; // ** Modified this line
console.log('a done');
b.js
(不变):
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b,a.done = %j',a.done);
exports.done = true;
console.log('b done');
main.js
(不变):
console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main,a.done = %j,a.done,b.done);
运行时:
main starting a starting b starting in b,a.done = undefined b done in a,b.done = true a done in main,a.done = undefined,b.done = true (node:2025) Warning: Accessing non-existent property 'done' of module exports inside circular dependency (Use `node --trace-warnings ...` to show where the warning was created)
侧面说明:使用JavaScript自己的模块系统(ESM)对循环的处理方式不同,并且由于没有exports
等效对象(您可以访问;从概念上讲),因此不会出现此问题。我建议尽可能使用ESM。自v12以来,Node.js就以相当稳定(尽管仍在不断发展)的方式对其进行了支持。