前言
本人记忆力一般,为了让自己理解《深入浅出Node.js-朴灵》一书,会在博客里记录一些关键知识,以后忘了也可以在这里找到,快速回想起来
Node通过require、exports、module实现CommonJS模块规范的
- 路径分析
require('http') //如http、fs、path,速度仅次于缓存加载,它在node源代码编译过程中已经被编译成二进制代码,其加载速度最快
require('./a.txt') //以.或者..开始的相对路径模块
require('/a.txt') //以/开始的绝对路径模块
//以上两种都当做文件模块来处理,在分析路径模块时require()方法会将路径转化为真实路径,并以真实路径为索引,将编译执行后的结果放到缓存中,以使二次加载更快。
//因为路径模块给了确切文件位置,所以在查找过程中可以节约大量时间,其加载慢于核心模块。
require(*) //非路径形式的文件模块,如自定义的connect模块//自己没太理解这块,因为可能没实现过自己的自定义模块所以举不出例子
//特殊的文件模块,可能是一个文件或者包的形式。这类模块查找最费时,也是所有方式中最慢的。原因是和js原型链一样要一层层node_modules找
2.文件定位
从缓存加载的优化策略使得二次引入不需要分析路径分析、文件定位和编译执行的过程,大大提高了再次加载时的效率
- 文件扩展名分析
require() 允许参数不带后缀,在这种情况下,node会按照.js、.json、.node次序补足扩展名依次尝试,在尝试过程中需要调用fs模块同步阻塞式判断文件是否存在。
所以在后两种引入方式时推荐加上后缀名
- 目录分析和包
require() 通过分析文件扩展名之后,可能没有查找到对应文件,但却得到一个目录,这在引入自定义模块和逐个模块路径进行查找时经常会出现,此时node会将目录当做一个包来处理。
在这个过程中,node对commonjs包规范进行了一定程度的支持。首先,node在当前目录下查找package.json,通过json.parse解析出包描述对象从中取出Main属性指定的文件名进行定位。
如果文件缺少扩展名,将会进入扩展名分析的步骤。
而如果main属性指定的文件名错误或者压根没有package.json文件,node将会将index当做默认文件名,依次添加扩展名查找
如果在目录分析的过程中没有定位成功任务文件则自定义模块进入下一个模块路径进行查找。如果模块路径数组都被遍历完毕,依然没有查找到目标文件,则会抛出查找失败异常。
3.编译执行
在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会新建一个模块对象,然后根据路径载入并编译。对于不同扩展名,其载入方式不一样如下:
.js 通过fs模块同步读取文件后编译执行
.node 这是通过c/c++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件
- .json 通过fs模块同步读取后,用JSON.parse解析返回结果
// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
var content = NativeModule.require('fs').readFileSync(filename, 'utf8');
try {
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
- 其余拓展名文件 都被当做js文件载入
每一个编译成功的模块都会将其文件路径作为索引缓存在Modules._cache对象上,以提高二次加载速度