前言
关于 Node.JS 中的 exports 和 module.exports, 很多时候都比较容易让人混淆, 弄不清楚两者间的区别. 那么我们就从头开始理清这两者之间的关系.
来源
在开发 Node.JS 应用的时候, 很多模块都是需要引入才能使用, 但是为什么 exports 和 module.exports 我们没有引用却可以直接使用呢?
事实上, Node.JS 应用在编译的过程中会对 JavaScript 文件的内容进行头尾的封装. 例如:
- // hello.JS
- const hello = function () {
- console.log('Hello world');
- }
- module.exports = {
- hello
- }
- // 头尾封装后的 JS 代码
- (function (exports, require, module, __filename, __dirname) {
- const hello = function () {
- console.log('Hello world');
- }
- module.exports = {
- hello
- }
- })
在进行了头尾封装之后, 各个模块之间进行了作用域隔离, 避免了污染全局变量, 同时可以使每个模块在不引入这些变量的情况下可以使用它们. 这些变量依次为当前模块的 exports 属性, require()方法, 当前模块自身(module), 在文件系统中的完整路径, 文件目录.
区别
按照 Node.JS 的解释, exports 是 module 对象的一个属性, 那么 exports 和 module.exports 应该是等价的. 的确如初, 初始化的 exports 和 module.exports 变量的值均为{}, 代码验证:
- // hello.JS
- const hello = function () {
- console.log('Hello world');
- }
- console.log('初始值 ==========');
- console.log(exports);
- console.log(module.exports);
- module.exports = {
- hello
- }
- // 输出结果
初始值 ==========
{}
{}
可以发现, module 对象的 exports 属性和 exports 均指向一个空对象{}, 那么在导出对象的时候使用 exports 和 module.exports 有什么区别呢?
我们在使用 require()方法引入模块的时候, 其实是引入了 module.exports 对象, exports 只是 module 对象的 exports 的一个引用, 我们可以通过修改 exports 所指向对象的值来协助修改 module.exports 的值.
使用 exports 导出
- const hello = function () {
- console.log('Hello world');
- }
- exports.hello = {
- hello
- }
- console.log('修改值 ==========');
- console.log(exports);
- console.log(module.exports);
- // 输出结果
修改值 ==========
- {
- hello: {
- hello: [Function: hello]
- }
- }
- {
- hello: {
- hello: [Function: hello]
- }
- }
由于 exports 和 module.exports 指向同一块内存区域, 所以我们修改 exports 对象的数据, 那么 module.exports 也会随之改变.
使用 module.exports 导出
- // hello.JS
- const hello = function () {
- console.log('Hello world');
- }
- module.exports = {
- hello
- }
- console.log('修改值 ==========');
- console.log(exports);
- console.log(module.exports);
- // 输出结果
修改值 ==========
- {
- }
- {
- hello: [Function: hello]
- }
你会发现修改后的 exports 依然是 {}, 而 module.exports 的值已经改变, 这是由于当你给 module.exports 是直接等于一个新的对象, 那么其将指向一块新的内存区域, 而此时 exports 指向的仍然是之前的内存区域, 所以二者的值会不一样, 但是此时你在其他文件内引入 hello.JS 文件, 仍然可以调用 hello() 方法, 这也说明了导出的是 module.exports 而不是 exports.
给 exports 直接赋值
- // hello.JS
- const hello = function () {
- console.log('Hello world');
- }
- exports = {
- hello
- }
- console.log('修改值 ==========');
- console.log(exports);
- console.log(module.exports);
- // 输出结果
修改值 ==========
- {
- hello: [Function: hello]
- }
- {
- }
使用这种方法导出在其他文件调用 hello 方法即会报错, 因为该文件模块导出的对象为空, 当然也不可能有 hello()方法, 这种问题的原因同样是指向的内存区域发生变化所导致的.
总结
exports 对象是 module 对象的一个属性, 在初始时 exports 和 module.exports 是指向同一块内存区域的;
在不改变 exports 内存指向的情况下, 修改 exports 的值可以改变 module.exports 的值;
导出尽量使用 module.exports 以避免混淆.
来源: https://juejin.im/post/5bc82e485188255c42585c02