与其他高级语言相比,如Java有类文件,Python有import机制,Ruby有require,PHP有include和require,JavaScript先天就缺乏的一项功能:模块
1 CommonJS规范
1.1 CommonJS的出发点
Web发展中,浏览器中出现了很多的标准API,这些过程发生在前端,后端JavaScript的规范却远远落后。主要有以下缺陷:
- 没有模块库
- 标准库较少。ECMAScript(官方规范)仅定义了部分核心库,对于文件系统、IO流等常见需求却没有标准的API。
- 没有标准接口
- 缺乏包管理系统
Node借鉴CommonJS的Modules规范实现了一套非常易用的模块系统,NPM对Packages规范的完好支持使得Node应用在开发过程中事半功倍。
1.2 CommonJS的模块规范
1.模块引用
var math = require('math');
使用require将模块引入到当前上下文。
2.模块定义
上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一导出的出口。在模块中,还存在一个module对象,它代表模块自身,而exports是module的属性。在Node中,一个文件就是一个模块。
// math.js
exports.add = function(){
var sum = 0,
i = 0,
args = arguments,
l = args.length;
while(i < 1){
sum += args[i++];
}
return sum;
}
3.模块标识
也就是require()内的参数,可以是相对路径、绝对路径。可以没有文件后缀.js。
2 Node的模块实现
Node对CommonJS的规范做了取舍。下面来具体看下在使用require,exports和module这些规范时,Node内部的实现过程。
在Node引入模块,需要经历以下3个步骤:
- 路径分析
- 文件定位
- 编译执行
在Node中,模块分为两类:
- Node提供的模块,即核心模块
- 用户编写的模块,即文件模块
核心模块在Node源代码编译过程中,编译进了二进制执行文件。在Node启动时,部分核心模块就直接被加载进内存。因此这部分模块不需要文件定位+编译过程,并且在路径分析中优先判断,因此加载速度最快的。
文件模块则在运行时动态加载,需要完整的路径分析、文件定位、编译执行的过程。
2.1 优先从缓存加载
require()方法对相同模块的二次加载都一律采用缓存优先的方式,这是第一优先级
的。核心模块的缓存检查优先于文件模块的缓存检查。
3 核心模块
前面提到,Node的核心模块在编译成可执行文件的过程中被编译进了二进制文件。
核心模块分为C/C++模块编写的和JavaScript编写的两部分。其中C/C++编写的存放在src目录下,JavaScript编写的存放在lib目录下。
4 包与NPM
4.1 包结构
包实际上是一个存档文件,即一个目录直接打包为.zip或tar.gz格式的文件,安装后解压还原为目录。符合CommonJS规范的目录应该包含如下文件:
- package.json:包描述文件;
- bin:存放可执行二进制文件的目录;
- lib:存放JavaScript代码;
- doc:存放文档
- test:存放单元测试代码
4.2 NPM常用功能
- 安装依赖包:npm install xxx
发布包:
- 编写模块;
- 初始化包描述文件:
npm init
- 注册包仓库账号:
npm adduser
- 上传包:
npm publish <folder>
。可以在刚刚创建的package.json文件所在目录下,执行npm publish .
,开始上传包 - 管理包权限
npm owner ls xxx
:列举出拥有包权限的人npm owner add <user> <package name>
npm owner rm <user> <package name>
分析包:
- 可使用
npm ls
命令生成包的依赖树。
局域NPM
- 对于企业级局域网NPM包,可搭建私有npm仓库。