标签引入而显得杂乱无章,语言本身毫无组织和约束能力。
经过了十多年的发展,CommonJs规范的提出算是一个重要的里程碑。
CommonJS规范为Javascript制定了一个美好的愿景,希望JavaScript能够在任何地方运行。
CommonJS将模块分为:
var math=require('math')
require()方法接受模块标识,以此引入一个模块的API到当前上下文中。
直接看代码:
//math.js
exports.add = function () {
var sum = 0,
i = 0,
args = arguments,
l = args.length;
while (i < l) {
sum += args[i++];
}
return sum;
};
在Node中,一个文件(比如math.js)就是一个module对象,它代表模块自身,而exports是module的属性。
上下文提供了exports对象用于导出当前模块的方法或者变量,并且它是唯一导出的出口。
在另一个文件中,通过require()方法引入模块后,就可以调用定义的属性或者方法了。比如:
// program.js
var math = require('math');
exports.increment = function (val) {
return math.add(val, 1);
};
模块标识其实就是传递给require()方法的参数,它必须是符合小驼峰命名的字符串,或者以..
或者.
开头的相对/绝对路径。它可以没有后缀名js。
尽管规范中的exports/require/module听起来十分简单,但是Node中引入模块,需要经历下面三个步骤:
1. 路径分析
2. 文件定位
3. 编译执行
在Node中,模块分为两类:一类是Node提供的模块,称为核心模块;另一类是用户编写的模块,称为文件模块。
Node对引入过的模块都会进行缓存,以减少二次引入时的开销。
不论是核心模块还是文件模块,require()方法对相同模块的二次加载都一律采用缓存优先的方式。
前面提到过require()接受一个标识符作为参数。模块标识符主要分为下面几类:
.
或..
开始的相对路径文件模块。/
开始的绝对路径文件模块。下面的方式实验:
(1)创建module_path.js文件,其内容为console.log(module.paths)
(2)将其放在任意一个目录然后执行node module_path.js
当前文件的路径越深,模块查找会越耗时,这是自定义模块的加载速度最慢的原因。
从缓存加载使得第二次引入不需要分析路径、文件定位和编译执行的过程。
但是在文件的定位过程中,还有一些细节需要注意,包括文件扩展名的分析、目录和包的处理。
CommonJS模块规范允许标识符中不包含文件扩展名,这种情况下Node会按照.js、.json、.node的次序补足扩展名,依次尝试。所以加上扩展名会加快一点速度。
如果在分析扩展名后,没有找到对应的文件但是找到了对应的目录,这时Node会将目录当作一个包来处理。
Node中每个文件模块都是一个对象,它的定义如下:
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
编译和执行是引入文件模块的最后一个阶段。针对不同的文件名,载入方法也不同:
Node组织了自身的核心模块,也使得第三方文件模块可以有序的编写和使用。但是在第三方模块中,模块与模块之间仍然是散列在各地的,相互之间不能直接引用。而在模块之外,包和NPM则是将模块联系起来的一种机制。
包的出现,则是在模块的基础上进一步组织JavaScript代码。
包实际上是一个存档文件,即一个目录直接打包为.zip或tar.gz文件。安装后解压还原为目录。
完全符合CommonJS规范的包目录应该包含如下这些文件:
NPM帮助Node完成了第三方模块的发布、安装和依赖。借助NPM,Node与第三方模块之间形成了很好的生态系统。
package.json文件定义了如下一些必须的字段: