大部分实现在module.js中,按照顺序来看: 首先调用require('xx')的时候内部调用了Module._load(path, parent) :
至于我们平时调用的require方法是不是就是这个Module原型上的require在稍后就可以确认。
Module.prototype.require = function(path) {
assert(path, 'missing path');
assert(util.isString(path), 'path must be a string');
return Module._load(path, this);
};
_load函数里处理了模块的缓存逻辑,这个大家知道下就行,接下来主要是这段逻辑
var module = new Module(filename, parent);
if (isMain) {
process.mainModule = module;
module.id = '.';
}
Module._cache[filename] = module;
var hadException = true;
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}
return module.exports;
可以看到try块中调用了module.load(file)方法,然后调用完就返回了module对象的exports属性,那继续查看load(file)方法是怎么加载这个file并且赋值给module.exports。
Module.prototype.load = function(filename) {
debug('load ' + JSON.stringify(filename) +
' for module ' + JSON.stringify(this.id));
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
};
直接看倒数4行,这几行就是根据要require的文件后缀来进行不同方法分发,我们这里就看最常见的对js文件的处理逻辑,其他后缀还支持'.node','.json'等。
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
};
对.js文件,就是直接读取了这个文件的文本内容做一些字符处理,然后调用_compile 方法,好了,马上就到终点了,直接看_compile(content,filename)方法。
_compile方法内容较长,但大部分不用特别深究,不影响理解require的逻辑。大致做了以下事情: 构建一个require变量
Module.prototype._compile = function(content, filename) {
var self = this;
// remove shebang
content = content.replace(/^\#\!.*/, '');
function require(path) {
return self.require(path);
}
require.resolve = function(request) {
return Module._resolveFilename(request, self);
};
Object.defineProperty(require, 'paths', { get: function() {
throw new Error('require.paths is removed. Use ' +
'node_modules folders, or the NODE_PATH ' +
'environment variable instead.');
}});
require.main = process.mainModule;
// Enable support to add extra extension types
require.extensions = Module._extensions;
require.registerExtension = function() {
throw new Error('require.registerExtension() removed. Use ' +
'require.extensions instead.');
};
require.cache = Module._cache;
还记得一开始的问题么,这个require对象就是Module.require,其实这个require对象就是我们平时调用的require('xxx')所用的那个。
然后有一段根据配置决定模块加载方式的,可以先不用看,因为默认不走这段逻辑,未避免分散注意力,这里就不贴了,有兴趣可以自己看环境变量:NODE_MODULE_CONTEXTS。
接下来才终于到了真正要进行动手装载模块的时候了,没几行代码:
var wrapper = Module.wrap(content);
这个wrap方法调用的是NativeModule.wrap方法,至于NativeModule.wrap方法究竟是干嘛的可看node.js源码:
NativeModule.wrap = function(script) {
return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
};
NativeModule.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
这个wrap方法非常重要!,直接解释了require核心是怎么实现的,其实就是把require的那个js文件的代码文本包在一个匿名函数体里。再看下这个自匿名函数的参数,也就是每个js模块中能直接访问到的变量。
接下去看:
这里调用了vm模块的runInThisContext,注意,这里并没有执行模块的代码哦,这里只是执行了wrap方法,这句运行完其实只是获得了一个匿名函数对象(之前只是代码文本)。
var compiledWrapper = runInThisContext(wrapper, { filename: filename });
接下去才是真正执行这个匿名函数。
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);
对应下这里的参数,这里可以很简单发现,首先每个模块的this就是module.exports(apply的第一个参数), 其次exports就是module对象上的exports,同一个东西。所以这里可以直接弄明白exports和module的关系了,可以直接在exports上加东西,或者直接换一个。然后传入的require就是_compile方法刚开始创建的那个require方法,执行逻辑是一致的,所以提供了模块加载的传递性。 所以在每个模块中直接去修改module对象上的exports就决定了最终模块返回的结果。
总结下,上面介绍的过程主要是最常见的加载非本地js模块的逻辑流程。根据代码可以知道每个模块中可以访问到的5个变量。了解了这些后对node中基本的module加载应该没不存在其他很大的疑惑了。