console.log(`Required invoked for module: ${moduleName}`);
const id = require.resolve(moduleName);
if(require.cache[id]) {
return require.cache[id].exports;
}
// module structure data
const module = {
exports: {},
id: id
}
// uodate cache
require.cache[id] = module;
// load the module
loadModule(id, module, require);
// return exported variables
return module.exports;
}
require.cache = {};
require.resolve = (moduleName) => {
// resolve a full module id from the moduleName
}
上面的函数模拟了Nodejs原生用来加载模块的require函数的行为,当然,它只是具有一个雏形,而没有完全准确的反映真实的require函数的行为,但是它可以让我们很好的理解Node模块系统的内部机制,一个模块怎么被定义和被夹在,我们的自制模块系统具备下面的功能
模块名被作为参数传入,首先要做的事情时调用require.resolve方法根据传入的模块名生成module id(通过指定的resolve算法来生成)
如果该模块已经被加载过了,那么直接会从缓存中获得
如果该模块还没有被加载过,我们会初始化一个module对象,其中包含两个属性,一个是module id,另外一个属性是exports,它的初始值为一个空对象,该属性会被用于保存模块的export的公共的API代码
将该module进行cache
调用我们上面定义的loadModule函数来获取模块的源代码,将初始化的module对象作为参数传入,因为module是对象,引用类型,所以模块可以利用module.exports或者是替换module.exports来暴露它的公共API
最后,返回给调用者module.exports的内容,也就是该模块的公共API
看到这里,我们会发现,其实在Node 模块系统没有想象中的那么难,真正的技巧在于将模块的代码进行包装,以及创建一个运行时的虚拟环境。
定义一个模块
通过观察我们自制的require()函数的工作机制,我们应该很清楚的知道如何定义一个模块
const dependency = require('./anotherModule');function log() {
console.log(`get another ${dependency.username}`);
}
module.exports.run = () => {
log();
}
// anotherModule.js
module.exports = {
username: 'wingerwang'
}
最重要的是要记住在模块里面,除了被分配给module.exports的变量,其他的都是该模块私有的,在使用require()加载后,这些变量的内容将会被缓存并返回。
定义全局变量
即使所有的变量和函数都在模块本身的作用域内声明的,但是仍然可以定义全局变量,事实上,模块系统暴露一个用来定义全局变量的特殊变量global,任何分配到这个变量的变量都会自动的变成全局变量









