Node.js 模块和包

一、简介

1、我们熟知的,在浏览器JavaScript中,脚本模块的拆分和组合通常都是使用HTML的script标签来实现的;而在Node.js中提供了require函数来调用其它的模块。

2、Node.js的模块都是基于文件的。

3、通常把Node.js的模块和包相提并论,因为模块和包是没有本质区别的。可以把包理解成是实现了某个功能模块的集合,用于发布和维护,但是对于使用者来说,模块和包的区别是透明的,所以通常不作区分。

4、Node.js的模块和包机制的实现参照了CommonJS标准,但未完全遵循。

二、模块

1、文件和模块是一一对应的,也就是说,一个Node.js文件就是一个模块。而该文件可以是JavaScript代码、JSON或者是编译过的C/C++扩展。

2、创建模块和加载模块

Node.js提供了exports和require两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块的接口,即获取模块的exports对象。如下代码是用exports对象创建了一个简单的自定义模块:

//module.js 创建自定义模块

var userName;

exports.setUserName = function(uname) {
	userName = uname;
};

exports.getUserName = function() {
	console.log("UserName is: " + userName);
}

那么,要如何在别的模块中加载此自定义模块,并使用该模块呢?如下代码所示:

//load_module.js 加载自定义模块

var myModule = require("./module");

myModule.setUserName("zhangsan");
myModule.getUserName();

其中的“require("./module")”就是加载我们自定义的模块module.js,这是一种相对路径的写法,也可以写成是绝对路径,比如module.js在我的本地路径是“E:\NodeDemo\module.js”(在Linux中肯定就是以/开头的绝对路径写法),那么可以使用如下的写法来加载该模块:

var myModule = require("E:/NodeDemo/module");

以上两种加载自定义模块的方式属于按路径加载,在这种方式下Node.js会按照优先级依次尝试加载require函数参数中最后一个“/”右边的那个单词的.js文件、.json文件和.node文件,比如上面的“/”右边的那个单词是module,那么Node.js会在对应目录下按照优先级依次尝试加载module.js文件、module.json文件,最后是module.node文件。

在以上的模块编写和加载方式中,通过把setUserName和getUserName函数赋给exports对象,作为模块的访问接口,而在加载了此模块后,就是获取了exports对象,则可以直接访问module模块中exports对象的成员函数了。上面的模块是把函数封装到模块中,而如果是要把一个对象赋给exports对象封装到模块中呢,又该怎么做?如下代码所示:

//module_object.js 封装一个对象到模块中

function User() {
	var userName;

	this.setUserName = function(userName) {
		this.userName = userName;
	};
	
	this.getUserName = function() {
		console.log("UserName is: " + this.userName);
	}
}

exports.userObject = User;

这就是将User对象赋值给exports对象的userObject属性,加载该模块并使用该模块中的User对象如下:

//load_module_object.js 加载自定义模块

var myModule = require("./module_object");

var user = new myModule.userObject();

user.setUserName("zhangsan");
user.getUserName();

这里通过require函数加载得到的仅仅是exports对象,要使用User对象,所以需要使用“new myModule.userObject()”的方式,有没有这种方式有点繁琐,所以可以简化模块,重新编写该模块如下:

//module_object.js 封装一个对象到模块中

function User() {
	var userName;

	this.setUserName = function(userName) {
		this.userName = userName;
	};
	
	this.getUserName = function() {
		console.log("UserName is: " + this.userName);
	}
}

module.exports = User;

注意这里最后一行的暴露模块接口的方式发生了变化,使用的是“module.exports = User”的方式,而在使用时就方便一些了,如下:

//load_module_object.js 加载自定义模块

var User = require("./module_object");

var user = new User();

user.setUserName("zhangsan2");
user.getUserName();

通过“module.exports = User”方式暴露模块接口后,使用require函数加载的模块得到的直接就是模块中的User对象,所以就可以直接使用了。

注意:模块接口的唯一变化是使用module.exports = User代替了之前的exports.userObject = User。在外部引用该模块时,其接口对象就是要输出的User对象本身,而不是原先的exports对象。事实上,exports本身仅仅是一个普通的空对象,即 {},它专门用来声明接口,本质上是通过它为模块闭包的内部建立了一个有限的访问接口。因为它没有任何特殊的地方,所以可以用其他东西来代替,譬如上面例子中的User对象。但是不可以通过对exports直接赋值代替对module.exports 赋值。因为exports实际上只是一个和module.exports指向同一个对象的变量,它本身会在模块执行结束后释放,但module不会,因此只能通过指定module.exports=xxx的方式来改变访问接口。

3、模块的单次加载

1、require不会重复加载模块,也就是说无论调用多少次require,获得的模块都是同一个。在如上的“load_module.js”模块加载“module.js”模块的示例中,修改“load_module.js”模块如下:

//load_module.js 模块的单次加载

var myModule01 = require("./module");
myModule01.setUserName("lisi");

var myModule02 = require("./module");
myModule02.setUserName("zhangsan");

myModule01.getUserName();

注意,这里使用require函数加载了两次module.js模块,且每次都调用module.js模块的setUserName函数并传入不同的参数值,但是在最后调用myModule01.getUserName输出的却是“UserName is: zhangsan”,如下图,这是因为变量myModule01和myModule02指向的是同一个实例,因此myModule01.setUserName的结果被myModule02.setUserName覆盖。

Node.js为什么不会重复加载模块呢?这是因为Node.js会通过文件名缓存所有加载过的文件模块,所以以后再访问到时就不会重新加载了。注意,Node.js是根据实际文件名缓存的, 而不是调用require()函数时提供的参数缓存的。

三、包

1、包是在模块基础上更深一步的抽象,Node.js的包其实就是类似于C/C++ 的函数库或者Java/.Net中的类库。

2、相比Node.js的模块是一个文件,Node.js 的包就是一个目录,其中包含一个JSON格式的包说明文件package.json。严格符合CommonJS规范的包应该具备以下特征:(但是Node.js对包的要求并没有这么严格,只要顶层目录下有 package.json,并符合一些规范即可。)

  • package.json必须在包的顶层目录下。
  • 二进制文件应该在bin目录下。
  • JavaScript 代码应该在lib目录下。
  • 文档应该在doc目录下。
  • 单元测试应该在test目录下。

3、创建包并导入包

既然模块与文件是一一对应的,那么把一个文件夹看成是一个文件也是可以的,所以一个文件夹也可以是一个模块,所以最简单的包,就是一个作为文件夹的模块。建立一个叫做node_package的文件夹,在其中创建index.js,内容如下:

//node_package/index.js 作为文件夹的模块

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

然后在node_package目录所在目录下建立一个load_package.js模块,内容如下:

//load_package.js 

var pkg = require('./node_package');
pkg.sayHello();

然后运行该文件就可以在控制台输出“Hello...”。使用这种方法可以把文件夹封装为一个模块,即所谓的包,包通常是一些模块的集合。那么这里Node.js为什么就能加载node_package文件夹下的index.js模块呢?这是因为Node.js在调用某个包时,会首先检查包中package.json文件的main字段,将其作为包的接口模块,如果package.json或main字段不存在,会尝试寻找包中顶层下的index.js或index.node作为包的接口。所以我们还可以采用在包中指定package.json的方式,比如把这里的index.js文件更名为interface.js并放入node_package的lib子文件夹下,并在node_package文件夹下建立一个package.json文件,内容如下:

{
	"main" : "./lib/interface.js"
}

然后load_package.js不变,运行load_package.js,依然可以在控制台上看见输出“Hello...”。

4、package.json文件内容字段

package.json是CommonJS规定的用来描述包的文件,完全符合规范的package.json文件应该含有以下字段:

  • name:包的名称,必须是唯一的,由小写英文字母、数字和下划线组成,不能包含空格。
  • description:包的简要说明。
  • version:符合语义化版本识别规范的版本字符串。
  • keywords:关键字数组,通常用于搜索。
  • maintainers:维护者数组,每个元素要包含name、email(可选)、web(可选)字段。
  • contributors:贡献者数组,格式与maintainers相同。包的作者应该是贡献者数组的第一个元素。
  • bugs:提交bug的地址,可以是网址或者电子邮件地址。
  • licenses:许可证数组,每个元素要包含type(许可证的名称)和 url(链接到许可证文本的地址)字段。
  • repositories:仓库托管地址数组,每个元素要包含type(仓库的类型,如git)、url(仓库的地址)和path(相对于仓库的路径,可选)字段。
  • dependencies:包的依赖,一个关联数组,由包名称和版本号组成。


 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值