module.exports 还是 exports?

原文

Node.js 通过实现 CommonJS 的 Modules/1.0 标准引入了模块(Module)概念,一个模块可以通过 module.exportsexports 将函数、变量等导出,以使其它 JavaScript 脚本通过 require() 函数引入并使用。

现在有一个问题:到底应该用 module.exports 还是用 exports 呢?

最好的方法就是查看 Node.js 的 官方文档中关于 module 的介绍

In particular module.exports is the same as the exports object.

可以看出 module.exportsexports 其实是一样的。在代码里比较:

1
2
console.log(module.exports === exports);
//输出结果也是 true。

然而参照 “module.exports vs exports in nodeJS” 中的例子,考虑下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// a1.js
exports = function () {
console.log('hi a2!');
}

// a2.js
module.exports = function () {
console.log('hi a2!');
}

// b.js
console.log(require('./a1'));
console.log(require('./a2'));

执行 node b,输出结果如下:

1
2
{}
[Function]

可见 a1.js 的 function 导出失败了,而 a2 却成功了。原因是什么呢?搜索无果后我直接查看了 Node.js 的源代码:

1
2
3
4
5
6
7
8
9
10
11
// file path: node/lib/module.js

// ...
global.require = require;
global.exports = self.exports;
global.__filename = filename;
global.__dirname = dirname;
global.module = self;

return runInThisContext(content, filename, true);
// ...

这段代码的作用是为 global 赋属性并调用 runInThisContext() 函数运行模块(其内容已经事先读入到 content 变量)。模块中使用的 exports (即 global.exports)其实是对 self.exports 的引用,对 exports 进行修改会影响到 self.exports,但如果对其赋值,exports 则不再是 self.exports 的引用了。而对 module.exports 赋值自然就没有问题。

了解这个,有些 Node.js 模块的源代码中的 exports = module.exports = someObject 也可以理解了:只使用 module.exports = someObject 的话 module.exports 引用的地址变了,而 exports 引用的还是之前的地址,所以下面的代码中再修改 exports 就不会再改变 module.exports 的值了。


总结

  • exportsmodule.exports的一个引用
  • require引用模块后,返回给调用者的是module.exports而不是exports
  • exports.xxx,相当于在导出对象上挂属性,该属性对调用模块直接可见
  • exports =相当于给exports对象重新赋值,调用模块不能访问exports对象及其属性
  • 如果此模块是一个类,就应该直接赋值module.exports,这样调用者就是一个类构造器,可以直接new实例