简单讲解一下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
| exports = { name: "nodejs" }
|
1 2 3 4 5 6 7 8 9 10 11
| let mode = { exp: {} } function load(exp,mode) { exp = {name: "nodejs"}
} load(mode.exp,mode) console.log(mode)
|
可以看到,直接对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)
|
上面的代码就可以成功执行了。
这就是为什么在nodejs中会有exports和module.exports两种方式来导出变量。但其实它们本质上是相等的,只是由于nodejs的模版编译机制的原因,不能直接对exports属性进行赋值导出。如果只需要导出一个数组或者函数,使用module.exports更方便。