node.js中模块和包

什么是模块

模块是 Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个
Node.js 文件就是一个模块,这个文件可能是 JavaScript 代码、JSON 或者编译过的 C/C++ 扩展。

如何创建并加载模块

介绍了什么是模块之后,下面我们来看看如何创建并加载它们。

1. 创建模块

在 Node.js 中,创建一个模块非常简单,因为一个文件就是一个模块,我们要关注的问
题仅仅在于如何在其他文件中获取这个模块。Node.js 提供了 exports 和 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.setName('BYVoid');
myModule.sayHello();

运行node getmodule.js,结果是:

Hello BYVoid

在以上示例中,module.js 通过 exports 对象把 setName 和 sayHello 作为模块的访
问接口,在 getmodule.js 中通过 require(’./module’) 加载这个模块,然后就可以直接访
问 module.js 中 exports 对象的成员函数了。
这种接口封装方式比许多语言要简洁得多,同时也不失优雅,未引入违反语义的特性,
符合传统的编程逻辑。在这个基础上,我们可以构建大型的应用程序,npm 提供的上万个模块都是通过这种简单的方式搭建起来的。

2. 单次加载

上面这个例子有点类似于创建一个对象,但实际上和对象又有本质的区别,因为
require 不会重复加载模块,也就是说无论调用多少次 require,获得的模块都是同一个。
我们在 getmodule.js 的基础上稍作修改:

//loadmodule.js
var hello1 = require('./module');
hello1.setName('BYVoid');
var hello2 = require('./module');
hello2.setName('BYVoid 2');
hello1.sayHello();

运行后发现输出结果是 Hello BYVoid 2,这是因为变量 hello1 和 hello2 指向的是
同一个实例,因此 hello1.setName 的结果被 hello2.setName 覆盖,最终输出结果是
由后者决定的。

3. 覆盖 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 来改变访问接口。

如何创建一个包

1. 作为文件夹的模块

模块与文件是一一对应的。文件不仅可以是 JavaScript 代码或二进制代码,还可以是一
个文件夹。最简单的包,就是一个作为文件夹的模块。下面我们来看一个例子,建立一个叫
做 somepackage 的文件夹,在其中创建 index.js,内容如下:

//somepackage/index.js
exports.hello = function() {
console.log('Hello.');
};
然后在 somepackage 之外建立 getpackage.js,内容如下:
//getpackage.js
var somePackage = require('./somepackage');
somePackage.hello();

运行 node getpackage.js,控制台将输出结果 Hello.
我们使用这种方法可以把文件夹封装为一个模块,即所谓的包。包通常是一些模块的集
合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库。通过定制package.json,我们可以创建更复杂、更完善、更符合规范的包用于发布。

2. 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 提供了命令行工具,使你可以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。

1. 获取一个包

使用 npm 安装包的命令格式为:

npm [install/i] [package_name]

例如你要安装 express,可以在命令行运行:

$ npm install express

或者:

$ npm i express

随后你会看到以下安装信息:

npm http GET https://registry.npmjs.org/express
npm http 304 https://registry.npmjs.org/express
npm http GET https://registry.npmjs.org/mime/1.2.4
npm http GET https://registry.npmjs.org/mkdirp/0.3.0
npm http GET https://registry.npmjs.org/qs
npm http GET https://registry.npmjs.org/connect
npm http 200 https://registry.npmjs.org/mime/1.2.4
npm http 200 https://registry.npmjs.org/mkdirp/0.3.0
npm http 200 https://registry.npmjs.org/qs
npm http GET https://registry.npmjs.org/mime/-/mime-1.2.4.tgz
npm http GET https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz
npm http 200 https://registry.npmjs.org/mime/-/mime-1.2.4.tgz
npm http 200 https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz
npm http 200 https://registry.npmjs.org/connect
npm http GET https://registry.npmjs.org/formidable
npm http 200 https://registry.npmjs.org/formidable
express@2.5.8 ./node_modules/express
-- mime@1.2.4
-- mkdirp@0.3.0
-- qs@0.4.2
-- connect@1.8.5

此时 express 就安装成功了,并且放置在当前目录的 node_modules 子目录下。npm 在
获取 express 的时候还将自动解析其依赖,并获取 express 依赖的 mime、mkdirp、qs 和 connect。

2. 本地模式和全局模式

npm在默认情况下会从http://npmjs.org搜索或下载包,将包安装到当前目录的node_modules子目录下。
【注】如果你熟悉 Ruby 的 gem 或者 Python 的 pip,你会发现 npm 与它们的行为不同,gem 或 pip 总是以全局模式安装,使包可以供所有的程序使用,而 npm 默认会把包安装到当前目录下。这反映了 npm 不同的设计哲学。如果把包安装到全局,可以提高程序的重复利用程度,避免同样的内容的多份副本,但坏处是难以处理不同的版本依赖。如果把包安装到当前目录,或者说本地,则不会有不同程序依赖不同版本的包的冲突问题,同时还减轻了包作者的 API 兼容性压力,但缺陷则是同一个包可能会被安装许多次。
在使用 npm 安装包的时候,有两种模式:本地模式和全局模式。默认情况下我们使用 npm install命令就是采用本地模式,即把包安装到当前目录的 node_modules 子目录下。Node.js 的 require 在加载模块时会尝试搜寻 node_modules 子目录,因此使用 npm 本地模式安装的包可以直接被引用。
npm 还有另一种不同的安装模式被成为全局模式,使用方法为:

npm [install/i] -g [package_name]

与本地模式的不同之处就在于多了一个参数 -g。
为什么要使用全局模式呢?多数时候并不是因为许多程序都有可能用到它,为了减少多
重副本而使用全局模式,而是因为本地模式不会注册 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命令了。

模式 可通过 require 使用 注册PATH
本地模式
全局模式

总而言之,当我们要把某个包作为工程运行时的一部分时,通过本地模式获取,如果要
在命令行下使用,则使用全局模式安装。

3. 创建全局链接

npm 提供了一个有趣的命令 npm link,它的功能是在本地包和全局包之间创建符号链
接。我们说过使用全局模式安装的包不能直接通过 require 使用,但通过 npm link命令
可以打破这一限制。举个例子,我们已经通过 npm install -g express 安装了 express,
这时在工程的目录下运行命令:
$ npm link express ./node_modules/express -> /usr/local/lib/node_modules/express
我们可以在 node_modules 子目录中发现一个指向安装到全局的包的符号链接。通过这
种方法,我们就可以把全局包当本地包来使用了。
然而npm link 命令不支持 Windows。

上一篇:PHP获取当期前运行文件的路径,名字,服务器路径


下一篇:PHP:如果正确加载js、css、images等静态文件