你肯定非常熟悉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。

深入理解node.js的module.export 和 export方法的区别的更多相关文章

  1. 理解Node.js的事件轮询

    前言 总括 : 原文地址:理解Node.js的事件轮询 Node小应用:Node-sample 智者阅读群书,亦阅历人生 正文 Node.js的两个基本概念 Node.js的第一个基本概念就是I/O操 ...

  2. 方便大家学习的Node.js教程(一):理解Node.js

    理解Node.js 为了理解Node.js是如何工作的,首先你需要理解一些使得Javascript适用于服务器端开发的关键特性.Javascript是一门简单而又灵活的语言,这种灵活性让它能够经受住时 ...

  3. 深入理解Node.js中的垃圾回收和内存泄漏的捕获

    深入理解Node.js中的垃圾回收和内存泄漏的捕获 文章来自:http://wwsun.github.io/posts/understanding-nodejs-gc.html Jan 5, 2016 ...

  4. Node.Js的Module System 以及一些常用 Module

    Node.Js学习就按照这本书的流程来. 在第7章结束与第10章结束时分别自己出一个小项目练练手.Node.Js的入门学习计划是这样. 目录:, QQ:1045642972 欢迎来索书以及讨论Node ...

  5. 如何理解Node.js和JavaScript的关系

    一.Javascript的引擎 浏览器一般有两个引擎,一个是Html引擎,一个是脚本引擎. JavaScript是一种脚本语言,最初用于浏览器的动态显示,方便操作页面数据和内容.但实际上,它也可以在浏 ...

  6. 使用Node.js给图片加水印的方法

    一.准备工作: 首先,确保你本地已经安装好了node环境. 然后,我们进行图像编辑操作需要用到一个Node.js的库:images. 这个库的地址是:https://github.com/zhangy ...

  7. JavaScript进阶(七)JS截取字符串substr 和 substring方法的区别

    JS截取字符串substr 和 substring方法的区别 substr方法 返回一个从指定位置开始的指定长度的子字符串. stringvar.substr(start [, length ]) 参 ...

  8. js正则表达式中test,exec,match方法的区别说明

    js正则表达式中test,exec,match方法的区别说明 test test 返回 Boolean,查找对应的字符串中是否存在模式.var str = "1a1b1c";var ...

  9. JS ,substr、 substring、charAt方法的区别

    JS 截取字符串substr 和 substring方法的区别,需要的朋友可以参考下,根据需要自行选择. substr 方法 返回一个从指定位置开始的指定长度的子字符串. stringvar.subs ...

随机推荐

  1. shell中如何取括号中的字符

    1. 使用grep(结果带括号,不知道有没有办法仅把括号中的内容匹配出来) $a='abc[edg]adfirpqu' $echo $a|grep -o '\[.*\]' #中括号的处理需要转义 [e ...

  2. 算法笔记_110:第四届蓝桥杯软件类省赛真题(JAVA软件开发高职高专组部分习题)试题解答

     目录 1 猜年龄 2 组素数 3 第39级台阶 4 核桃的数量 5 打印十字图 6 买不到的数目   前言:以下试题解答代码部分仅供参考,若有不当之处,还请路过的同学提醒一下~ 1 猜年龄 标题: ...

  3. 网站中超链接方式直接添加QQ好友

    使用情景: 在图中点击图片,会弹出添加qq好友的窗口进行好友添加. 链接如下: tencent://AddContact/?fromId=45&fromSubId=1&subcmd=a ...

  4. HDU 1023 Train Problem II 大数打表Catalan数

    一个出栈有多少种顺序的问题.一般都知道是Catalan数了. 问题是这个Catalan数非常大,故此须要使用高精度计算. 并且打表会速度快非常多.打表公式要熟记: Catalan数公式 Cn=C(2n ...

  5. redission 分布式锁

    https://my.oschina.net/haogrgr/blog/469439   分布式锁和Redisson实现 Aug 20, 2017 CONTENTS 概述 分布式锁特性 Redis实现 ...

  6. 修改模块搜索路径(注意:py运行完毕,就会失效,他只是临时产生效果)

    # -*- coding: utf-8 -*- #python 27 #xiaodeng #修改模块搜索路径(注意:py运行完毕,就会失效,他只是临时产生效果) import sys print sy ...

  7. Tomcat 签名认证配置简例

    在项目的web.xml中增加 <security-constraint> <web-resource-collection> <web-resource-name> ...

  8. 史上最全的iOS面试题及答案,且看且珍藏,错过就没有喽!

    1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答:Object-c的类不可以多重继承;可以实现多个接口,通过实现多 ...

  9. Ant build xml中的各种变量解释

    Ant build.xml中的各种变量 Ant环境变量分为四种: 1.      build.properties文件中定义的变量 2.      build.xml文件中定义的变量, 3.      ...

  10. 漫谈Github与开源,Git介绍以及Git的思想和基本工作原理 Git工作流程

    漫谈Github与开源 文字亮点: 为什么这些优秀的工程师会开源自己的项目? 因为开源是一种精神. 无数的软件开发者苦心积虑保护自己的代码不被破解,而还是被聪明绝顶的脚本小子破解了,但破解无数软件的脚 ...