nodejs的exports和module.exports属性

简单讲解一下js中为什么会有两种模块导出成员变量的方法,以及它们的关联性。

首先nodejs的模块加载机制是对commonjs的模块机制进行了实现。在nodejs中每个模块都是一个对象,它的最终定义如下:

1
2
3
4
5
6
7
8
9
10
11
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
可以看到其中的 this.exports 属性,它就是我们在导出模版变量时用到的 exports 属性。 我们知道每个模版文件中存在 require、exports、module 这3个变量,它们在模块文件中并没有被定义,它们从何而来?我们还知道每个文件还有 __dirname,__filename 这两个变量,它们又是从何而来?事实上在模版的编译过程中,nodejs对模块文件进行了包装执行。它的包装格式如下:
1
2
3
4
5
(function (exports, require, module, __filename, __dirname) { 
var math = require('math');
exports.area = function (radius) {
return Math.PI * radius * radius; };
});
上面就可以清楚看到这个五个变量的由来,它们都是nodejs编译模块时候自动传入的函数形参,所以我们在js中可以自由的使用它们。

上面的exports属性就是我们在导出成员变量时所使用的exports,它本质上就是第三个参数module变量的exports属性,也就是说,
exports === mudule.exports 。这个判断你放在nodejs中执行也是true。也就是说nodejs明面上让我们用exports属性来导出模版的变量,那为什么我们还要用module.exports呢,因为exports不能直接赋值,比如下面的代码:

1
2
3
4
// 这段代码你在另一个文件引用,是拿不到name变量的,为什么呢?通过下面一个的代码例子来了解:
exports = {
name: "nodejs"
}
1
2
3
4
5
6
7
8
9
10
11
// 这里我们模拟nodejs的模版编译:function (exports, require, module, __filename, __dirname),只使用了两个变量
let mode = {
exp: {}
}
function load(exp,mode) {
exp = {name: "nodejs"}

}
load(mode.exp,mode)
console.log(mode) // { exp: {} }

可以看到,直接对exp进行赋值,是不能修改mode的exp属性的,只能使用exports.xxx的方式来导出变量。因为exp是mode.exp属性的引用的拷贝。函数内修改它的引用对象,不能对外部起作用。然后我们对mode.exp进行赋值修改:
1
2
3
4
5
6
7
8
9
let mode = {
exp: {}
}
function load(exp,mode) {
mode.exp = {name: "nodejs"}

}
load(mode.exp,mode)
console.log(mode) // { exp: { name: 'nodejs' } }
上面的代码就可以成功执行了。 这就是为什么在nodejs中会有exports和module.exports两种方式来导出变量。但其实它们本质上是相等的,只是由于nodejs的模版编译机制的原因,不能直接对exports属性进行赋值导出。如果只需要导出一个数组或者函数,使用module.exports更方便。

nodejs的exports和module.exports属性

http://itpika.com/2020/02/08/js/exports-module/

作者

itpika

发布于

2020-02-08 14:23:21

更新于

2021-01-18 14:13:01

许可协议

评论