看源码 lib/module.js
下面的代码去掉了作者的注释和断言,只保留和 module 相关的
引用一个模块,最先调用的就是require
方法:
Module.prototype.require = function(path) {
return Module._load(path, this);
};
可见,require
其实内部调用了Module._load
方法。接下来看_load
方法:
Module._load = function(request, parent, isMain) {
// 确定模块的绝对路径
var filename = Module._resolveFilename(request, parent);
// 第一步:如果有缓存,取出缓存
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
// 第二步:是否为内置模块
if (NativeModule.exists(filename)) {
return NativeModule.require(filename);
}
// 第三步:生成模块实例,存入缓存
var module = new Module(filename, parent);
Module._cache[filename] = module;
// 第四步:加载模块
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}
// 第五步:输出模块的exports属性
return module.exports;
};
上面代码中,首先解析出模块的绝对路径(filename),以它作为模块的识别符。然后,如果模块已经在缓存中,就从缓存取出;如果不在缓存中,就加载模块。
由第五步可见,require
方法能看到的只有module.exports
这个对象,它是看不到exports
对象的。而我们在编写模块时用到的exports
对象实际上只是对module.exports
的引用,这体现在Module
对象的_compile
方法:
// self 指的就是 Module
global.exports = self.exports;
源码中,module.exports
初始的时候置为{}
,exports
也指向这个空对象。
那么,这样写是没问题的,因为都是修改的同一内存地址里的东西:
exports.name = function(x){
console.log(x);
};
module.exports.name = function(x){
console.log(x);
};
但是这样写就有了区别了:
exports = function(x){
console.log(x);
};
module.exports = function(x){
console.log(x);
};
上面的function
是一块新的内存地址,导致exports
与module.exports
不存在任何关系,而require
方法能看到的只有module.exports
这个对象,看不到exports
对象,所以上面的写法是导不出去的。而下面的写法是可以导出去的。