深入理解node.js的module.export 和 export方法的区别

你肯定非常熟悉nodejs模块中的exports对象,你可以用它创建你的模块。例如:(假设这是rocker.js文件)

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

在另一个文件中你这样引用

var rocker = require('./rocker.js');
rocker.name(); // 'My name is Lemmy Kilmister'

那到底Module.exports是什么呢?它是否合法呢?

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

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

修改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导出,除非你打算从原来的“实例化对象”改变成一个类型

 

 

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实例

客官如果看明白咋回事儿了下面的内容可以忽略:)

假如有模块a.js代码如下:

exports.str = 'a';  
exports.fn = function() {};  

对a模块的调用:

var a = require('./a');  
console.log(a.str);  
console.log(a.fn());  

这样用是对的,如果改造a如下:

exports.str = 'a';  
exports = function fn() {};  

在调用a模块时自然没用fn属性了。

再改造下a模块:

exports.str = 'a';  
module.exports = function fn() {};  

这时a模块其实就是fn函数的引用,也就是说可以require('./a')()这样使用,而同时不再有str属性了。

下面直接导出一个类:

module.exports = function A() {};  

调用:

var A = require('./a');  
var a = new A();  

总结下,有两点:

  1. 对于要导出的属性,可以简单直接挂到exports对象上

  2. 对于类,为了直接使导出的内容作为类的构造器可以让调用者使用new操作符创建实例对象,应该把构造函数挂到module.exports对象上,不要和导出属性值混在一起

 

 

很多新手可能会迷惑于 exports 和 module.exports 的区别,为了更好的理解 exports 和 module.exports 的关系,我们先来巩固下 js 的基础。示例:

test.js

var a = {name: 1};
var b = a;

console.log(a);
console.log(b);

b.name = 2;
console.log(a);
console.log(b);

var b = {name: 3};
console.log(a);
console.log(b);

运行 test.js 结果为:

{ name: 1 }
{ name: 1 }
{ name: 2 }
{ name: 2 }
{ name: 2 }
{ name: 3 }

解释:a 是一个对象,b 是对 a 的引用,即 a 和 b 指向同一块内存,所以前两个输出一样。当对 b 作修改时,即 a 和 b 指向同一块内存地址的内容发生了改变,所以 a 也会体现出来,所以第三四个输出一样。当 b 被覆盖时,b 指向了一块新的内存,a 还是指向原来的内存,所以最后两个输出不一样。

明白了上述例子后,我们只需知道三点就知道 exports 和 module.exports 的区别了:

  1. module.exports 初始值为一个空对象 {}
  2. exports 是指向的 module.exports 的引用
  3. require() 返回的是 module.exports 而不是 exports

Node.js 官方文档的截图证实了我们的观点:

exports = module.exports = {...}

我们经常看到这样的写法:

exports = module.exports = {...}

上面的代码等价于:

module.exports = {...}
exports = module.exports

原理很简单:module.exports 指向新的对象时,exports 断开了与 module.exports 的引用,那么通过 exports = module.exports 让 exports 重新指向 module.exports。

posted @ 2017-02-16 13:52  axl234  阅读(14008)  评论(0编辑  收藏  举报