module.exports 与 exports区别


在浏览器端js里面,为了解决各模块变量冲突等问题,往往借助于js的闭包把所有模块相关的代码都包装在一个匿名函数里。而Node.js编写模块相当的自由,开发者只需要关注require,exports,module等几个变量就足够,而为了保持模块的可读性,很推荐把不同功能的代码块都写成独立模块,减少各模块耦合。开发者可以在“全局”环境下任意使用var申明变量(不用写到闭包里了),通过exports暴露接口给调用者。

我们经常看到类似export.xxx = yyy或者module.exports = xx这样的代码,可实际在通过require函数引入模块时会出现报错的情况,这是什么原因导致的呢?

Node.js在模块编译的过程中会对模块进行包装,最终会返回类似下面的代码:

(function (exports, require, module, __filename, __dirname) {
    // module code...
});

其中,module就是这个模块本身,require是对Node.js实现查找模块的模块Module._load实例的引用,filename和dirname是Node.js在查找该模块后找到的模块名称和模块绝对路径,这就是官方API里头这两个全局变量的来历。

关于module.exports与exorts的区别,了解了下面几点之后应该就完全明白:

模块内部大概是这样:

exports = module.exports = {};
exports是module.exports的一个引用

require引用模块后,返回给调用者的是module.exports而不是exports exports.xxx,相当于在导出对象上挂属性,该属性对调用模块直接可见 exports =相当于给exports对象重新赋值,调用模块不能访问exports对象及其属性 如果此模块是一个类,就应该直接赋值module.exports,这样调用者就是一个类构造器,可以直接new实例 客官如果看明白咋回事儿了下面的内容可以忽略:)

其实,Module.exports才是真正的接口,exports只不过是它的一个辅助工具。 最终返回给调用的是Module.exports而不是exports。

所有的exports收集到的属性和方法,都赋值给了Module.exports。当然,这有个前提,就是Module.exports本身不具备任何属性和方法。如果,Module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略。

demo 示例

新建rocker.js如下:

module.exports = 'ROCK IT!';
exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};
再次引用执行rocker.js
var rocker = require('./rocker.js');
rocker.name(); // TypeError: Object ROCK IT! has no method 'name'

发现报错:对象“ROCK IT!”没有name方法

rocker模块忽略了exports收集的name方法,返回了一个字符串“ROCK IT!”。由此可知,你的模块并不一定非得返回“实例化对象”。你的模块可以是任何合法的javascript对象--boolean, number, date, JSON, string, function, array等等。

你的模块可以是任何你设置给它的东西。如果你没有显式的给Module.exports设置任何属性和方法,那么你的模块就是exports设置给Module.exports的属性。

下面例子中,你的模块是一个类:

module.exports = function(name, age) {
    this.name = name;
    this.age = age;
    this.about = function() {
        console.log(this.name +' is '+ this.age +' years old');
    };
};

复制代码 可以这样应用它:

var Rocker = require('./rocker.js');
var r = new Rocker('Ozzy', 62);
r.about(); // Ozzy is 62 years old

下面例子中,你的模块是一个数组:

module.exports = ['Lemmy Kilmister', 'Ozzy Osbourne', 'Ronnie James Dio', 'Steven Tyler', 'Mick Jagger'];

可以这样应用它:

var rocker = require('./rocker.js');
console.log('Rockin in heaven: ' + rocker[2]); //Rockin in heaven: Ronnie
James Dio

现在你明白了,如果你想你的模块是一个特定的类型就用Module.exports。如果你想的模块是一个典型的“实例化对象”就用exports。

给Module.exports添加属性类似于给exports添加属性。例如:

module.exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

同样,exports是这样的

exports.name = function() {
    console.log('My name is Lemmy Kilmister');
};

请注意,这两种结果并不想同。前面已经提到module.exports是真正的接口,exports只不过是它的辅助工具。推荐使用exports导出,除非你打算从原来的“实例化对象”改变成一个类型。