一、简介
Node.js采用CommonJS规范来组织模块,即在每个文件中定义模块,通过exports对象或module.exports对象对外暴露接口,通过require函数来引用其他模块。
在Node.js内部,处理模块加载的主要逻辑被封装在模块内部模块cjs的loader.js文件中。该文件的作用是确定模块的文件路径和文件名,以及使用该路径来读取文件内容并在代码中执行该模块。因此,深入了解internal/modules/cjs/loader.js文件对于了解Node.js模块加载机制非常重要。
二、分析内部模块cjs的loader.js文件
loader.js文件将Node.js的模块加载机制进行了抽象,可以分为以下几个步骤:
1. 从module对象中获取模块信息
function tryModuleLoad(module, filename) {
var threw = true;
try {
module.load(filename);
threw = false;
} finally {
if (threw) {
delete Module._cache[filename];
}
}
}
该函数的作用是尝试加载模块。
首先从Module._cache对象中获取模块,如果缓存中存在模块,则跳过加载步骤,直接返回该模块。
如果缓存中不存在模块,则根据文件名解析出完整文件路径,并捕获可能的异常。
如果异常被捕获,将删除缓存中的模块信息。
如果没有异常,将调用模块对象的load方法实现模块加载和执行。
2. 获取模块对应的文件路径
function resolveFilename(request, parentModule, isMain) {
var resolvedModule = Module._resolveFilename(request, parentModule, isMain);
return resolvedModule;
}
该函数的作用是获取一个请求模块路径的完整路径名。
该方法内部调用了Module._resolveFilename方法,通过路径分析算法找到对应的文件,查找路径顺序为:
- 原始文件路径
- 系统模块
- node_modules文件夹
3. 加载模块
Module.prototype.load = function(filename) {
var extension = pathModule.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
};
该方法的作用是加载模块。
首先通过node的pathModule模块获取文件扩展名,再根据扩展名查找对应的Module._extensions对象中的方法,如果找到对应的方法,则执行该方法加载模块。
例如,若文件扩展名为.js,则调用Module._extensions['.js']。
4. 编译模块
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
}
该函数的作用是读取文件,并将读取到的内容作为字符串编译成可执行的代码。
代码读取使用了Node.js的文件系统模块(fs模块)中的readFileSync()方法。
读取出来的内容会有BOM标记,所以需要stripBOM()函数将其去除,该函数被定义在Module.js文件中。
最后,执行module._compile()函数将字符串转换成可执行的代码并存储在module.exports对象中。
三、总结
Node.js的内部模块cjs的loader.js文件是Node.js的模块加载机制的核心部分,它负责解析模块的文件路径和读取文件内容,并将内容编译成可执行的代码。对Node.js的模块加载机制进行深入的了解,有助于开发者更好地理解Node.js的模块化开发,进而开发出更好的应用程序。