模块(Module)和包(Package)是Node.js最重要的支柱。开发一个具有一定规模的程序不可能只用一个文件,通常需要把各个功能拆分,封装,然后组合起来,模块正是为了实现这种方式而诞生的。在浏览器Javascript中,脚本模块的拆分和组合通常使用HTML的script标签来实现。Node.js提供了require函数来调用其他模块,而且模块都是基于文件的,机制十分简单。
Node.js中的模块和包机制的实现参照了CommonJS的标准,但并未完全遵循,不过两者的区别不大,一般来说你大可不必担心,只有当你试图制作一个除了支持Node.js之外还要支持其他平台模块或者包的时候才需要研究。通常两者没有直接冲突的地方。
通常会把Node.js的包和模块相提并论,因为模块和包没有本质的区别,两个概念也时常混用,如果要辨析,可以把包理解成实现了某个功能模块的集合,用于发布和维护。对使用者来说,模块和包的区别是透明的,因此经常不做区分。
什么是模块?
模块是Node.js应用程序的基本组成部分,文件和模块是一一对应的,换言之,一个Node.js文件就是一个模块,这个文件可能是JS代码、JSON或者编译过得C/C++扩展。
例如:var http = require('http')
其中http是Node.js的一个核心模块,其内部是用C++实现的,外部用JS封装。我们 通过require函数获取了这个模块,然后才能使用其中的对象。
模块的创建与加载
Node.js中模块的创建十分简单,因为一个文件就是一个模块,要关注的问题就是如何在其他文件中获取这个模块,Node.js中提供了express 和 require两个对象,其中exports是模块公开的额接口,require则用于从外部获取一个模块的接口,即所获模块的exports对象。
下面以一个例子来了解模块。创建一个module.js文件,内容如下:
//module.js
var name;
exports.setName = function(thyName){
name = thyName;
};
exports.sayHello = function(){
console.log('Hello'+name);
};
在同一目录下创建getmodule.js内容如下:
// getmodule.js
var myModule = require('./module');
myModule.segName('BYVoid');
myModule.sayHello();
运行node getmodule.js , 结果是: Hello BYVoid
上例子中,module.js通过exports对象把setName和sayHello作为模块的访问接口,在getmodule.js中通过require('./module')加载这个模块,然后就可以直接访问modul.js中exports对象的成员函数。
这种接口封装方式比很多语言要简洁的多,同时不失优雅,未引入违反语义的特性,符合传统的编程逻辑,在这个基础上,我们可以构建大型的应用软件,npm提供上万个模块都是通过这种简单的方式搭建起来。
单次加载
无论调用多少次require,获得的模块都是同一个。
覆盖exports
有时候我们只是想把一个对象封装到模块中,例如:
// singleobject.js
function Hello(){
var name;
this.setName = function (thyName){
name = thyName;
};
this.sayHello = function(){
console.log('Hello ' + name);
};
};
exports.Hello = Hello;
在其他文件中需要通过require('./singleobject').Hello 来获取Hello对象,略显冗余,可以通过以下方法来简化。
//hello.js
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;
这样就可以直接获得这个对象了:
//gethello.js
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello();
注意,模块接口的唯一变化是使用 module.exports = Hello 代替了 exports.Hello=
Hello。在外部引用该模块时,其接口对象就是要输出的 Hello 对象本身,而不是原先的
exports。
事实上,exports 本身仅仅是一个普通的空对象,即 {},它专门用来声明接口,本
质上是通过它为模块闭包①的内部建立了一个有限的访问接口。因为它没有任何特殊的地方,
所以可以用其他东西来代替,譬如我们上面例子中的 Hello 对象。
不可以通过对 exports 直接赋值代替对 module.exports 赋值。
exports 实际上只是一个和 module.exports 指向同一个对象的变量,
它本身会在模块执行结束后释放,但 module 不会,因此只能通过指定
module.exports 来改变访问接口。