前言 在上一篇文章中,我们介绍了 JavaScript 模块系统的发展历程 ,其中提到了 CommonJS 是 Node.js 默认的模块系统。在 CommonJS 中,我们使用 module.exports
和 exports
来导出模块内容,使用 require
来导入模块。但很多初学者对 module.exports
和 exports
的关系和区别感到困惑。本文将深入剖析这两个对象的关系和正确使用方式。
理解 module 对象 在每个 Node.js 文件中,都有一个内置的 module
对象。这个对象包含了当前模块的相关信息。
让我们通过一个简单的例子来观察 module
对象:
运行这个文件:
你会看到类似以下的输出:
1 2 3 4 5 6 7 8 9 10 Module { id : '.' , path : '/path/to/your/file' , exports : {}, parent : null , filename : '/path/to/your/file/example.js' , loaded : false , children : [], paths : [ ] }
可以看到,module
是一个包含多个属性的对象,其中 exports
是一个空对象 {}
。这个 exports
对象就是我们用来导出模块内容的地方。
module.exports 和 exports 的关系 最关键的一点是:**exports
是 module.exports
的引用**。在 Node.js 执行你的代码之前,它会悄悄地添加以下代码:
1 2 var module = new Module (...);var exports = module .exports ;
这就像下面的代码一样:
让我们通过一个例子来验证这一点:
1 2 3 4 5 6 7 8 9 10 console .log ("module.exports === exports:" , module .exports === exports ); console .log ("module.exports:" , module .exports ); console .log ("exports:" , exports ); exports .name = "Node.js" ;console .log ("修改后 module.exports:" , module .exports ); console .log ("修改后 exports:" , exports );
可以看到,修改 exports
会同时影响 module.exports
,因为它们指向同一个对象。
正确使用 module.exports 和 exports 1. 使用 exports 添加属性 最常见的方式是使用 exports
给模块添加属性或方法:
1 2 3 4 5 6 7 8 9 10 exports .add = function (a, b ) { return a + b; }; exports .subtract = function (a, b ) { return a - b; }; exports .PI = 3.14159 ;
在其他文件中使用:
1 2 3 4 const utils = require ("./utils" );console .log (utils.add (2 , 3 )); console .log (utils.PI );
2. 使用 module.exports 导出整个对象 当你想要导出一个完整的对象时,应该使用 module.exports
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function add (a, b ) { return a + b; } function subtract (a, b ) { return a - b; } module .exports = { add : add, subtract : subtract, PI : 3.14159 , };
或者导出一个类:
1 2 3 4 5 6 7 8 9 10 11 function Person (name, age ) { this .name = name; this .age = age; } Person .prototype .greet = function ( ) { return `Hello, I'm ${this .name} ` ; }; module .exports = Person ;
使用类:
1 2 3 4 const Person = require ("./Person" );const person = new Person ("Alice" , 30 );console .log (person.greet ());
3. 混合使用(推荐方式) 在同一个模块中,可以同时使用 module.exports
和 exports
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 exports .version = "1.0.0" ;exports .author = "Developer" ;module .exports = { add : function (a, b ) { return a + b; }, multiply : function (a, b ) { return a * b; }, };
常见错误用法 错误 1:直接给 exports 赋值 1 2 3 4 5 6 7 exports = { name : "Wrong Way" , method : function ( ) { return "This will not work" ; }, };
这种做法是错误的,因为这会改变 exports
的指向,但它不再是 module.exports
的引用了。当你在其他文件中 require 这个模块时,得到的是一个空对象 {}
。
1 2 3 const wrong = require ("./wrong1" );console .log (wrong);
错误 2:混合使用时先重新赋值 module.exports 1 2 3 4 5 6 7 exports .helper = "Helper function" ;module .exports = function mainFunction ( ) { return "Main function" ; };
这种情况下,exports.helper
不会被导出,因为 module.exports
被重新赋值了。
最佳实践 1. 保持一致性 在一个模块中,尽量保持使用同一种方式导出:
1 2 3 4 5 6 7 8 9 10 module .exports = { method1 : function ( ) { }, method2 : function ( ) { }, property : "value" , };
2. 导出类或构造函数时使用 module.exports 1 2 3 4 5 6 7 8 9 10 function Database (url ) { this .url = url; } Database .prototype .connect = function ( ) { }; module .exports = Database ;
3. 导出单个函数时使用 module.exports 1 2 3 4 module .exports = function greet (name ) { return `Hello, ${name} !` ; };
4. 导出多个相关函数时使用 exports 1 2 3 4 5 6 7 8 9 10 exports .add = function (a, b ) { return a + b; }; exports .subtract = function (a, b ) { return a - b; }; exports .multiply = function (a, b ) { return a * b; };
实际应用示例 让我们看一个更完整的示例,展示如何在实际项目中使用这些概念:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function Database (config ) { this .host = config.host ; this .port = config.port ; this .name = config.name ; } Database .prototype .connect = function ( ) { console .log (`Connecting to ${this .host} :${this .port} /${this .name} ` ); }; Database .prototype .disconnect = function ( ) { console .log ("Disconnecting from database" ); }; module .exports = Database ;exports .createConnection = function (config ) { return new Database (config); };
使用这个模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const Database = require ("./database" );const { createConnection } = require ("./database" );const db1 = new Database ({ host : "localhost" , port : 5432 , name : "mydb" , }); const db2 = createConnection ({ host : "localhost" , port : 5432 , name : "mydb" , });
总结 理解 module.exports
和 exports
的关键点:
关系 :exports
是 module.exports
的引用,就像 var a = {}; var b = a;
一样
本质 :require()
返回的是 module.exports
,而不是 exports
使用建议 :
导出多个属性或方法时,可以使用 exports
导出类、构造函数或单一对象时,推荐使用 module.exports
避免直接给 exports
赋值
在同一模块中尽量保持导出方式的一致性
通过正确理解和使用 module.exports
和 exports
,你可以更好地组织和导出 Node.js 模块,构建更加清晰和可维护的代码结构。
如果你想了解更多关于 JavaScript 模块系统的内容,可以阅读我们之前的文章 JavaScript CommonJS 和 ES Module 理解与应用 。