解析Node.js中module.exports的用途与使用方法

解析Node.js中module.exports的用途与使用方法

技术背景

在Node.js编程中,模块化是非常重要的编程理念。模块化允许开发者将程序拆分成多个独立的文件,每个文件包含特定功能的代码。module.exportsexports 是实现模块化的核心机制,它们基于CommonJS模块规范,被很多其他的JavaScript实现所支持,如RequireJS、CouchDB等。

实现步骤

导出多个函数

在模块文件中,可以通过 exports 变量将内部作用域的函数暴露出去,在调用代码中使用 require() 引入模块并访问其导出的函数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// mymodule.js
let myFunc1 = function() {
console.log('Function1');
};
let myFunc2 = function() {
console.log('Function2');
};
exports.myFunc1 = myFunc1;
exports.myFunc2 = myFunc2;

// main.js
const m = require('./mymodule');
m.myFunc1();
m.myFunc2();

此时,require 的结果是一个普通对象,可直接访问其属性。

导出单个函数

若希望 require() 调用返回一个可调用的函数而不是一个对象,需要使用 module.exports。例如:

1
2
3
4
5
6
7
8
// sayhello.js
module.exports = exports = function() {
console.log("Hello World!");
};

// app.js
var sayHello = require('./sayhello.js');
sayHello();

核心代码

简单计数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// counter.js
var count = 1;

exports.increment = function() {
count++;
};

exports.getCount = function() {
return count;
};

// app.js
var counting = require('./counter.js');

console.log(counting.getCount());
counting.increment();
console.log(counting.getCount());

遵循CommonJS规范示例

1
2
3
4
5
6
7
8
// sayhello.js
exports.run = function() {
console.log("Hello World!");
}

// app.js
var sayHello = require('./sayhello');
sayHello.run();

最佳实践

  • 避免覆盖 exportsexports 只是 module.exports 的引用,覆盖 exports 会打破引用关系,应仅向其添加属性。
    • 好的示例
1
2
3
4
5
exports.name = 'william';

exports.getName = function(){
console.log(this.name);
}
- **坏的示例**:
1
2
3
4
exports = 'william';
exports = function(){
//...
}
  • 针对不同场景选择合适的导出方式:当导出多个函数或对象时,使用 exports 会更自然;当只导出一个函数、对象时,应使用 module.exports
  • 使用ES6特性:对于符合ES6规范的模块,可以使用 Object.assign 简化代码。
1
2
3
4
5
6
7
8
9
10
// sayhello.js
Object.assign(exports, {
sayhello() {
console.log("Hello World!");
}
});

// app.js
const { sayhello } = require('./sayhello');
sayhello();
  • 路径设置:使用 require() 引入模块时,注意设置正确的路径,可通过 NODE_PATH 环境变量设置,或在 require 中使用相对路径。

常见问题

覆盖 exportsmodule.exports 的问题

  • 覆盖后,之前附加到原始 exportsmodule.exports 的所有属性和方法都会丢失。
1
2
3
4
5
6
7
8
9
10
exports.method1 = function () {};
exports.method2 = function () {};

module.exports.method3 = function () {};

var otherAPI = {
// some properties and/or methods
};

exports = otherAPI;
  • exportsmodule.exports 引用了新值后,它们不再指向同一个对象。
1
2
3
4
5
exports = function AConstructor() {}; 
exports.method2 = function () {};

// 此方法添加到原始exports对象,但不再暴露
module.exports.method3 = function () {};

同时修改 exportsmodule.exports 的引用

如果同时改变 exportsmodule.exports 的引用,难以确定最终暴露的是哪个API,但通常 module.exports 会生效。

1
2
3
module.exports = function AConstructor() {};

exports = function AnotherConstructor() {};

ES6模块与CommonJS模块并存的问题

从Node 14.0开始,ECMAScript模块不再是实验性特性。ECMAScript模块与CommonJS模块有诸多差异,如不再有 requireexportsmodule.exports。但Node.js支持两者并存,并提供了一定的互操作性。在合适的时候,可以根据项目需求和开发规范选择适合的模块系统。


解析Node.js中module.exports的用途与使用方法
https://119291.xyz/posts/nodejs-module-exports-usage-analysis/
作者
ww
发布于
2025年5月30日
许可协议