当我们调用require(...)
, 它到底是怎么样的一个过程?
require(...) 加载模块
const mod = require('./a.js');
通过跟踪代码,探究一下背后的过程。 注意,下面的代码经过严重简化。
1. require函数
require = function require(path) {
return mod.require(path);
};
2. mod.require函数
Module.prototype.require = function(id) {
return Module._load(id, this, /* isMain */ false);
};
3. Module._load函数
Module._load = function(request, parent, isMain) {
// 获取文件路径
const filename = Module._resolveFilename(request, parent, isMain);
// 原生模块对象
if (NativeModule.nonInternalExists(filename)) {
debug('load native module %s', request);
return NativeModule.require(filename);
}
// 第三方模块创建模块对象,包括node_modules下和src下的
var module = new Module(filename, parent);
// 加载模块
module.load(filename);
return module.exports;
}
Module._resolveFilename 函数的作用是找到文件路径。
Module._resolveFilename 函数依赖父模块 parent, 比如父模块的路径为 /project/src/index.js, 那么 require('./a.js') 是相对于父模块的路径来查找的:
require('./a') --> /project/src/a.js require('express') --> /project/node_modules/express/index.js ...
以 require('express')
为例,将会以如下顺序查找文件,如果文件存在则返回文件决定路径:
- node_modules/express.js
- node_modules/express.json
- node_modules/express.node
- node_modules/express/package.json 返回main字段指定文件
- node_modules/express/index.js
4. mod.load函数
Module.prototype.load = function(filename) {
const extension = findLongestRegisteredExtension(filename);
Module._extensions[extension](this, filename);
}
根据文件后缀执行不同的操作,比如 .js、.json、.node(),json 文件会直接读取文件内容,JSON.parse 直接输出, node 文件会使用 process.dlopen() 执行文件。
5. 对js文件处理的部分
Module._extensions['.js'] = function(module, filename) {
content = fs.readFileSync(filename, 'utf8');
module._compile(content, filename);
}
对于 js 文件,先读取 js 代码内容,然后编译代码。
6. mod._compile函数
Module.prototype._compile = function(content, filename) {
var wrapper = Module.wrap(content);
var compiledWrapper = vm.runInThisContext(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true
});
compiledWrapper.call(
this.exports,
this.exports, // 模块代码运行会在this.exports上添加变量,这个也是require(...)返回的结果
require,
this,
filename,
dirname
);
}
编译代码主要用到 vm 模块,将代码编译成字节码,让v8虚拟机运行。
Module.wrap
const Module = require('module');
Module.wrap('console.log(1);');
// (function (exports, require, module, __filename, __dirname) { console.log(1);
// });
Module.wrap的作用就是给代码外包裹一个函数。
所以我们在写 nodejs 代码时可以直接使用 exports、require、module、__filename、__dirname 而不需要 require, 因为它们已经被传入。
vm.runInThisContext
vm.runInThisContext(...)
相当于 script = vm.Script(...); script.runInThisContext(...)
的组合。
main模块的加载
当我们运行node index.js
, 会调用runMain函数:
// lib/internal/modules/run_main.js
function executeUserEntryPoint(main = process.argv[1]) {
Module._load(main, null, true);
}
在内部直接调用 Module._load,因为它是第一个被调用的模块,所以参数 parent 为 null, isMain 为 true。
参考链接: