前言:工欲善其事,必先利其器。模块系统是nodejs组织管理代码的利器也是调用第三方代码的途径,本文将详细讲解nodejs的模块系统。在文章最后实例分析一下exprots和module.exprots。

nodejs的模块

什么是模块?

node.js通过实现CommonJS的Modules/1.0标准引入了模块(module)概念,模块是Node.js的基本组成部分.一个node.js文件就是一个模块,也就是说文件和模块是一一对应的关系.这个文件可以是JavaScript代码,JSON或者编译过的C/C++扩展.

Node.js的模块分为两类,一类为原生(核心)模块,一类为文件模块。

在文件模块中,又分为3类模块。这三类文件模块以后缀来区分,Node.js会根据后缀名来决定加载方法。

  • .js。通过fs模块同步读取js文件并编译执行。
  • .node。通过C/C++进行编写的Addon。通过dlopen方法进行加载。
  • .json。读取json文件,调用JSON.parse解析加载。

Node提供了exports和require两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块接口,即所获取模块的exports对象


require和exports

require

require函数用于在当前模块中加载和使用别的模块,传入一个模块名,返回一个模块导出对象。require方法接受以下几种参数的传递:

  • http、fs、path等。原生模块。
  • ./mod或../mod。相对路径的文件模块。
  • /a/mod,绝对路径的文件模块。
  • mod,非原生模块的文件模块。

exports

exports对象是当前模块的导出对象,用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时得到的就是当前模块的exports对象。

module

通过module对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出对象

  • module.exports :{Object}类型,模块系统自动产生。

  • module.require(id)

id {String} 
Return: {Object} 已解析模块的 module.exports 
这个方法提供了一种像 require() 一样从最初的模块加载一个模块的方法。

    • module.id:{String}类型,用于区别模块的标识符。通常是完全解析后的文件名

    • module.filename:{String}类型,模块完全解析后的文件名。

    • module.loaded:{Boolean}类型,判断该模块是否加载完毕。

    • module.parent:{Module Object}类型,返回引入了本模块的其他模块。

    • module.children:{Array}类型,该模块所引入的其他子模块。

demo1 module.exports的使用

sayHello.js:

function sayHello() {
console.log('hello');
} module.exports = sayHello;

app.js:

var sayHello = require('./sayHello');
sayHello(); //hello

代码讲解:

定义一个sayHello模块,模块里定义了一个sayHello方法,通过替换当前模块exports对象的方式将sayHello方法导出。

在app.js中加载这个模块,得到的是一个函数,调用该函数,控制台打印hello。

demo2 匿名替换

sayWorld.js

module.exports = function () {
console.log('world');
}

app.js

var sayWorld = require('./sayWorld');
sayWorld(); //world

代码讲解

与上面稍有不同,这次是匿名替换。

demo3 替换为字符串

不仅可以替换为方法,也可以替换为字符串等。

stringMsg.js

module.exports = 'i am a string msg!';

app.js

var string = require('./stringMsg');
console.log(string); //i am a string msg!


demo4 exports导出多个变量

当要导出多个变量怎么办呢?这个时候替换当前模块对象的方法就不实用了,我们需要用到exports对象。

useExports.js

exports.a = function () {
console.log('a exports');
} exports.b = function () {
console.log('b exports');
}

app,js

var useExports = require('./useExports');
useExports.a();
useExports.b();
//a exports
//b exports
当然,将useExports.js改成这样也是可以的:
module.exports.a = function () {
console.log('a exports');
} module.exports.b = function () {
console.log('b exports');
}

下面通过gif图进行演示:

module.exports和exports在文章的最后会进行详细讲解。

模块初始化

一个模块中的JS代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象。之后,缓存起来的导出对象被重复利用。

举个例子,count,js:

var i = 0;

function count() {
return ++i;
} exports.count = count;

  

app.js

var c1 = require('./count');
var c2 = require('./count'); console.log(c1.count());
console.log(c2.count());
console.log(co2.count());
//1
//2
//3

可以看到,count.js并没有因为被require了两次而初始化两次。

主模块

通过命令行参数传递给NodeJS以启动程序的模块被称为主模块。主模块负责调度组成整个程序的其它模块完成工作。例如通过以下命令启动程序时,我们刚刚一直使用的app.js就是主模块。

二进制模块

虽然一般我们使用JS编写模块,但NodeJS也支持使用C/C++编写二进制模块。编译好的二进制模块除了文件扩展名是.node外,和JS模块的使用方式相同。虽然二进制模块能使用操作系统提供的所有功能,拥有无限的潜能,但对于不熟悉C/C++的人而言编写过于困难,并且难以跨平台使用,因此本文不作讲解。

模块的加载优先级

由于Node.js中存在4类模块(原生模块和3种文件模块),尽管require方法极其简单,但是内部的加载却是十分复杂的,其加载优先级也各自不同,下面是require加载的逻辑图:

原生模块在Node.js源代码编译的时候编译进了二进制执行文件,加载的速度最快。另一类文件模块是动态加载的,加载速度比原生模块慢。但是Node.js对原生模块和文件模块都进行了缓存,于是在第二次require时,是不会有重复开销的。

exports与module.exports

这里可能是最容易混淆的地方了。

我们先来看一个例子:

modOne.js

exports.hello = function () {
console.log("hello");
} module.exports = function () {
console.log('world');
}

app.js

var one = require('./modOne');

//one.hello(); //执行这句话会报错one.hello is not a function

one() //打印world

这是为什么呢?我们得先从exports 与module.exports 说起。

其实,exports 是module.exports的一个引用,exports 的地址指向module.exports。

而我们的modOne.js中通过module.exports = function的方式将module.exports给替换掉了。

而require方法所返回的是module.exports这个实实在在的对象,但是它已经被替换成了function,这就导致了exports指向了空,所以,你所定义的exports.hello是无效的。

用一个通俗易懂的例子来重新解释一遍。

比如你在电脑的D盘下新建了一个exports文本文档,然后你右键->发送到桌面快捷方式。

D盘就相当于nodejs中的module,这个exports文本文档就相当于nodejs中模块的exports对象,快捷方式就相当于nodejs中指向exports对象引用

D:/exportes.txt ==> module.exportes 
exportes.txt快捷方式 ==> exportes

然后,你看exportes.txt不爽,把它给删了,然后新建了一个word文档–exports.docx。

这个时候你桌面上的快捷方式就没用了,虽然也叫exports,但是你是访问不到这个新的word文件的。

对于nodejs也一样,当你把module.exportes对象覆盖了,换成了其他东西的时候,exportes这个引用就失效了。

同样,我们还可以用这个例子来理解为什么exportes也可以用来导出模块。

我们是这样使用exportes的:

exports.hello = function () {
console.log("hello");
}

这段代码其实等同于:

module.exports.hello = function () {
console.log("hello");
}

怎么理解呢。还是刚才的txt文件,这次没有删除。

D:/exportes.txt ==> module.exportes 
exportes.txt快捷方式 ==> exportes

你在桌面打开了exportes.txt快捷方式,然后在里面输入hello,然后保存,关闭。

你再打开D:/exportes.txt,你会发现你可以看到刚刚写的hello,你又在后面添加了一句“world”,保存关闭。

返回桌面,打开快捷方式,你会看到helloworld。

所以说你使用’exports.属性’和’module.exportes.属性’是等同的。

这也就能很好的解释下面这个问题了:

exports = function() {
console.log('hello');
} //这样写会报错

这样相当于把exprots这个引用覆盖掉了,你把txt文件的快捷方式改成docx的快捷方式还能打开原来的txt文件么?显然是不能的。

最后做一个总结:

当我们想让模块导出的是一个对象时, 使用exports 和 module.exports 都可以(但 exports 也不能重新覆盖为一个新的对象),而当我们想导出非对象接口时,就必须也只能覆盖 module.exports 。

nodejs的模块系统(实例分析exprots和module.exprots)的更多相关文章

  1. nodejs基础 -- 模块系统

    为了让nodejs的文件可以相互调用,nodejs提供了一个简单的模块系统. 模块:是nodejs应用程序的基本组成部分,文件和模块一一对应.即,一个nodejs文件就是一个模块,这个文件可能是jav ...

  2. Nodejs的模块系统以及require的机制

    一.简介 Nodejs 有一个简单的模块加载系统.在 Nodejs 中,文件和模块是一一对应的(每个文件被视为一个独立的模块),这个文件可能是 JavaScript 代码,JSON 或者编译过的C/C ...

  3. Nodejs的模块系统

    global对象 浏览器端JavaScript中的全局对象为"window",在浏览器中定义的变量都会成为"window"对象的属性. 不像浏览器端JavaSc ...

  4. 前端笔记之NodeJS(二)路由&REPL&模块系统&npm

    一.路由机制(静态资源文件处理) 1.1 Nodejs没有根目录 MIME类型:http://www.w3school.com.cn/media/media_mimeref.asp 在Apache中, ...

  5. node(基础三)_模块系统基础

      一.前言                                                                                         这篇文章主 ...

  6. ABP文档笔记 - 模块系统 及 配置中心

    ABP框架 - 模块系统 ABP框架 - 启动配置 Module System Startup Configuration ABP源码分析三:ABP Module ABP源码分析四:Configura ...

  7. [Abp vNext 源码分析] - 2. 模块系统的变化

    一.简要说明 本篇文章主要分析 Abp vNext 当中的模块系统,从类型构造层面上来看,Abp vNext 当中不再只是单纯的通过 AbpModuleManager 来管理其他的模块,它现在则是 I ...

  8. [Abp 源码分析]二、模块系统

    0.简介 整个 Abp 框架由各个模块组成,基本上可以看做一个程序集一个模块,不排除一个程序集有多个模块的可能性.可以看看他官方的这些扩展库: 可以看到每个项目文件下面都会有一个 xxxModule ...

  9. Linux系统网络性能实例分析

    由于TCP/IP是使用最普遍的Internet协议,下面只集中讨论TCP/IP 栈和以太网(Ethernet).术语 LinuxTCP/IP栈和 Linux网络栈可互换使用,因为 TCP/IP栈是 L ...

随机推荐

  1. 魔方阵算法及C语言实现

    1 魔方阵概念 填充的,每一行.每一列.对角线之和均相等的方阵,阶数n = 3,4,5….魔方阵也称为幻方阵. 例如三阶魔方阵为: 魔方阵有什么的规律呢? 魔方阵分为奇幻方和偶幻方.而偶幻方又分为是4 ...

  2. Android 自学之选项卡TabHost

    选项卡(TabHost)是一种非常实用的组件,TabHost可以很方便地在窗口上放置多个标签页,每个标签页相当于获得了一个与外部容器相同大小的组建摆放区域.通过这种方式,就可以在一个容器中放置更多组件 ...

  3. LeetCode 274

    H-Index Given an array of citations (each citation is a non-negative integer) of a researcher, write ...

  4. Linux下RPM软件包的安装及卸载

    http://os.51cto.com/art/201001/177866.htm 在 Linux 操作系统下,几乎所有的软件均通过RPM 进行安装.卸载及管理等操作.RPM 的全称为Redhat P ...

  5. CSS3 垂直树状图——运用 :before 和 :after

    直接上图(原网址),还有步骤想详解视频.自己CSS3练习demo. [demo] [HTML] <div class="tree"> <ul> <li ...

  6. spring分布式事务学习笔记

    最近项目中使用了分布式事务,本文及接下来两篇文章总结一下在项目中学到的知识. 分布式事务对性能有一定的影响,所以不是最佳的解决方案,能通过设计避免最好尽量避免. 分布式事务(Distributed t ...

  7. 对XML的操作

    对XML的操作主要使用到的语法示例: using System.Xml; private static string XmlMarketingStaff = AppDomain.CurrentDoma ...

  8. ASP判断当前页面上是否有参数ID传递过来

    遇到了一个这样的ASP问题: 在当前页面上判断,是否有参数ID传递过来? 如果没有,显示“没有参数传递过来”. 如果有传递,但值为空,显示“存在参数,但参数为空” <% if (request( ...

  9. 160多个android开源代码汇总

    第一部分 个性化控件(View) 主要介绍那些不错个性化的View,包括ListView.ActionBar.Menu.ViewPager.Gallery.GridView.ImageView.Pro ...

  10. Asp.Net运行原理(=)

    浏览器与服务器之间的通信. 一般浏览器与服务器之间的底层是通过socket建立连接的. 当浏览器与服务器之间建立了socket连接之后,服务器就开始监听. 当浏览器与服务器之间建立了相互兼容的协议之后 ...