目录
一、什么是模块
二、创建并加载模块
1.创建模块
2.单次加载
3.覆盖exports
三、创建一个包
1.作为文件夹的模块
2.package.json
四、使用包管理器
1.获取一个包
2.本地模式和全局模式
模块(Module)和包(Package)是Node.js最重要的支柱。
开发一个具有一定规模的程序通常需要把各个功能拆分、封装,然后组合起来,模块正是为了实现这种方式而诞生的。
在浏览器JavaScript中,脚本模块的拆分和组合通常使用HTML的script标签来实现。Node.js提供了require函数来调用其他模块,而且模块都是基于文件的,机制十分简单。Node.js的模块和包机制的实现参照了CommonJS的标准,但并未完全遵循。
模块和包是没有本质区别的,两个概念也时常混用。如果要辨析,那么可以把包理解成是实现了某个功能模块的集合,用于发布和维护。对使用者来说,模块和包的区别经常不作区分。
模块是Node.js应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个Node.js文件就是一个模块,这个文件可能是JavaScript代码、JSON或者编译过的C/C++扩展。例如,http是Node.js的一个核心模块,其内部是用C++实现的,外部用JavaScript封装。先通过require函数获取了这个模块,然后才能使用其中的对象。
在Node.js中,创建一个模块比较容易,因为一个文件就是一个模块。
Node.js提供了exports和require两个对象,其中exports是模块公开的接口,require用于从外部获取一个模块的接口,即所获取模块的exports对象。
举个例子:例<1>在同一个文件下建立module.js和getmodule.js文件
//module.js var name; exports.setName = function(thyName){ name = thyName; } exports.sayHello = function(){ console.log("Hello " + name); }
//getmodule.js var myModule = require('./module') myModule.setName('aaa') myModule.sayHello();
运行后的结果是:
在这个例子中,module.js通过exports对象把setName和sayHello作为模块的访问接口,在getmodule.js中通过require('./module')加载这个模块,然后就可以直接访问module.js中exports对象的成员函数。
在上面的例子中,require不会重复加载模块,也就是说,无论调用多少次require,获得的模块都是同一个。
将例<1>中的getmodule.js修改一下:
例<2>:
//getmodule.js var hello1 = require('./module') hello1.setName('aaa') var hello2 = require('./module') hello2.setName('bbb') hello1.sayHello();
运行结果如下:
修改后的getmodule.js的运行结果是“Hello bbb”,因为变量hello1和hello2指向的是同一个实例,因此hello1.setName的结果被hello2.setName覆盖,最终输出结果是由后者决定的。
把一个对象封装到模块中:
//singleobject.js function Hello(){ var name; this.setName = function(thyName){ name = thyName; } this.sayHello = function(){ console.log('Hello ' + name); } } exports.Hello = Hello;
获取Hello对象时需要通过 require('./singleobject').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('aaa') hello.sayHello()
运行结果如下:
模块接口的唯一变化是使用module.exports = Hello代替了exports.Hello=Hello。
在外部引用该模块时,其接口对象就是要输出的Hello对象本身,而不是原先的exports。
事实上,exports本身仅仅是一个普通的空对象,即{ },它专门用来声明接口,本质上是通过它为模块闭包的内部建立了一个有限的访问接口。因为它没有任何特殊的地方,所以可以用其他东西来代替,譬如上面例子中的Hello对象。
包是在模块基础上更深一步的抽象,Node.js的包类似于C/C++的函数库或者Java/.Net的类库。它将某个独立的功能封装起来,用于发布、更新、依赖管理和版本控制。
Node.js根据CommonJS规范实现了包机制,开发了npm来解决包的发布和获取需求。Node.js的包是一个目录,其中包含一个JSON格式的包说明文件package.json。
严格符合CommonJS规范的包应该具备以下特征:
模块与文件是一一对应的。
文件不仅可以是JavaScript代码或二进制代码,还可以是一个文件夹。最简单的包,就是一个作为文件夹的模块。
举个例子:建立一个叫做somepackage的文件夹,在其中创建index.js:
//somepackage/index.js exports.hello =function(){ console.log('Hello.') }
然后在somepackage这个文件夹的外面建立getpackage.js :’
//getpackage.js var somePackage = require('./somepackage') somePackage.hello()
运行结果如下:
使用这种方法可以把文件夹封装为一个模块,即所谓的包。
包通常是一些模块的集合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库。通过定制package.json,可以创建更复杂、更完善、更符合规范的包用于发布。
在前面例子中的somepackage文件夹下,我们创建一个叫做package.json的文件,内容如下所示:
{ "main" : "./lib/interface.js" }
然后将index.js重命名为interface.js并放入lib子文件夹下。以同样的方式再次调用这个包,依然可以正常使用。
Node.js在调用某个包时,会首先检查包中package.json文件的main字段,将其作为包的接口模块,如果package.json或main字段不存在,会尝试寻找index.js或index.node作为包的接口。
package.json是CommonJS规定的用来描述包的文件,完全符合规范的package.json文件应该含有以下字段:
Node.js包管理器(即npm),是Node.js官方提供的包管理工具,它已经成了Node.js包的标准发布平台,用于Node.js包的发布、传播、依赖控制。npm提供了命令行工具,可以方便地下载、安装、升级、删除包,也可以作为开发者发布并维护包。
使用npm安装包的命令格式为:npm [install/i] [package_name]
eg:安装express,可以在命令行运行: npm install express 或者 npm i express
npm在默认情况下会从http://npmjs.org搜索或下载包,将包安装到当前目录的node_modules子目录下。
在使用npm安装包的时候,有两种模式:本地模式和全局模式。
默认情况下我们使用npm install命令就是采用本地模式,即把包安装到当前目录的node_modules子目录下。
Node.js的require在加载模块时会尝试搜寻node_modules子目录,因此使用npm本地模式安装的包可以直接被引用。
npm还有另一种不同的安装模式被成为全局模式
使用方法为:npm [ install / i ] -g [ package_name ]
全局模式与本地模式的不同之处就在于多了一个参数-g。
模式 | 可通过require使用 | 注册PATH |
本地模式 | 是 | 否 |
全局模式 | 否 | 是 |
Q :为什么要使用全局模式呢?
A :多数时候并不是因为许多程序都有可能用到它,为了减少多重副本而使用全局模式,而是因为本地模式不会注册PATH环境变量。
举例说明,我们安装supervisor是为了在命令行中运行它,譬如直接运行supervisor script.js,这时就需要在PATH环境变量中注册supervisor。npm本地模式仅仅是把包安装到node_modules子目录下,其中的bin目录没有包含在PATH环境变量中,不能直接在命令行中调用。而当我们使用全局模式安装时,npm会将包安装到系统目录,譬如/usr/local/lib/node_modules/,同时package.json文件中bin字段包含的文件会被链接到/usr/local/bin/。/usr/local/bin/是在PATH环境变量中默认定义的,因此就可以直接在命令行中运行supervisor script.js命令了。
总而言之,当我们要把某个包作为工程运行时的一部分时,通过本地模式获取;如果要在命令行下使用,则使用全局模式安装。