Node.js(3)— 模块和包 及其创建和使用

目录

一、什么是模块

二、创建并加载模块

  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函数获取了这个模块,然后才能使用其中的对象。

二、创建并加载模块

  1.创建模块

        在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();

运行后的结果是:

 Node.js(3)— 模块和包 及其创建和使用

        在这个例子中,module.js通过exports对象把setNamesayHello作为模块的访问接口,在getmodule.js中通过require('./module')加载这个模块,然后就可以直接访问module.js中exports对象的成员函数

  2.单次加载 

        在上面的例子中,require不会重复加载模块,也就是说,无论调用多少次require,获得的模块都是同一个。

        将例<1>中的getmodule.js修改一下:

        例<2>:

//getmodule.js
var hello1 = require('./module')
hello1.setName('aaa')
var hello2 = require('./module')
hello2.setName('bbb')
hello1.sayHello();

 运行结果如下:

Node.js(3)— 模块和包 及其创建和使用

        修改后的getmodule.js的运行结果是“Hello bbb”,因为变量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;

        获取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()

        运行结果如下:

 Node.js(3)— 模块和包 及其创建和使用

        模块接口的唯一变化是使用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规范的包应该具备以下特征:

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

  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.js(3)— 模块和包 及其创建和使用

        使用这种方法可以把文件夹封装为一个模块,即所谓的包。

        包通常是一些模块的集合,在模块的基础上提供了更高层的抽象,相当于提供了一些固定接口的函数库。通过定制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文件应该含有以下字段:

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

四、使用包管理器

        Node.js包管理器(即npm),是Node.js官方提供的包管理工具,它已经成了Node.js包的标准发布平台,用于Node.js包的发布、传播、依赖控制。npm提供了命令行工具,可以方便地下载、安装、升级、删除包,也可以作为开发者发布并维护包

  1.获取一个包

        使用npm安装包的命令格式为:npm [install/i] [package_name]

        eg:安装express,可以在命令行运行: npm install express 或者 npm i express

  2.本地模式和全局模式

        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命令了。

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

上一篇:6.Node.js模块系统简介


下一篇:npm学习笔记