CommonJS规范
Javascript作为前端语言一直发展的很好,但是作为后端JavaScript的规范却远远落后。
- 没有模块系统
- 标准库较少
- 没有标准接口
- 没有包管理系统
CommonJS规范覆盖了
- 模块
- 二进制
- Buffer
- 字符集编码
- I/O流
- 进程环境
- 文件系统
- 套接字
- 单元测试
- Web网管接口
- 包管理
CommonJS的模块规范
模块规范主要分为 模块引用、模块定义、模块标识三部分
-
模块引用
var math = require('math');
require方法接受一个模块标识,以此引入一个模块的API到当前上下文
-
模块定义
模块中,上下文提供了require方法来引入外部模块。上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一导出的出口。在模块中,还存在一个module对象,它代表模块自身,而exports是module的属性。在Node中,一个文件就是一个模块,将方法和变量挂在exports对象作为属性即可定义导出的方法:
// math.js exports.add= function(){ var sum = 0, i = 0, args = arguments, l = args.length; while(i < 1){ sum +=args[i++]; } return sum; }
在另外一个文件中,我们通过require()方法引入模块后
var math = require('math'); exports.increment = function(val){ return math.add(val, 1); }
-
模块标识
模块标识其实就是传递给require()方法的参数,它必须是符合小骆驼命名的字符串,或者. ..开头的地址,它可以是没有后缀.js
Node的模块实现
node中引入模块,经历如下三个步骤:
- 路径分析
- 文件定位
- 编译执行
模块分为两类: 一类是Node提供的模块,称为核心模块。另一类是用户编写的模块,称为文件模块。
-
核心模块
核心模块是Node源代码,编译进二进制执行文件。Node进程启动时,部分核心模块直接加载入内存。所以文件定位和编译执行两步骤可以省略掉,并且在路径分析优先判断
-
文件模块
在运行时动态加载,需要完整的路径分析,文件定位,编译执行,速度比核心模块慢
优先从缓存加载
Node缓存的是编译和执行之后的对象
缓存加载中核心模块优先文件模块。
路径分析和文件定位
- 模块标识符
-
核心模块 如http fs path等
加载的优先级仅此缓存加载,文件是二进制代码
. .. 开始的相对路径文件模块
-
以/开始的绝对路径文件模块
被当做文件模块来处理,require方法会将路径转为真实路径,将编译执行后的结果存放在缓存中,使第二次加载更快
非路径形式的文件模块
模块路径的生成规则如下
- 当前文件目录中的node_modules目录
- 父目录的node_modules目录
- 沿路径向上逐级递归
-
文件定位
-
文件扩展名分析
Node会按照.js .node .json的次序补足扩展名,依次尝试
-
目录分析和包
require可能没找到对应文件,首先node会当前目录下查找Package.json,从中取出Main属性指定的文件名定位,如果没有package.json,Node会将index当做默认文件名,然后依次查找index.js index.node index.json
-
-
JavaScript模块的编译
在编译的过程中,JS代码会被进行头尾包装
(function(exports, require, module, __filename, __dirname){ var math = require('math'); exports.area = function(radus){ return Math.PI * radius * radius } })
至此,require、exports、module的流程已经完整
模块调用栈
JavaScript核心模块主要扮演的职责有两类:
- 作为c/c++内置模块的封装层和桥接层
- 纯粹的功能模块