Node.js design pattern 一书中对 Node 的 Module 模块机制这一块,我觉得讲的挺透彻和易懂,这里根据自己理解做下总结.本文转发自本人 github .
loadModule
自定义一个简单的模块加载方法 loadModule,基本思路跟 nodejs 一致,将加载的模块内容包裹在一个函数里面实现变量的隔离,保证模块内的变量都是私有的.
这个例子通过 eval 对 wrappedSrc 进行计算,即通过 eval 函数处理该字符串脚本.因为这里要把
function loadModule(filename, module, require) {
const wrappedSrc = `(function(module, exports, require) {
${fs.readFileSync(filename, 'utf8')}
})(module, module.exports, require);`;
eval(wrappedSrc);
}
(function(module, exports, require) {
这串东西和
fs.readFileSync(filename, 'utf8')
加载的模块内容合并在一起作为新的整合代码再运行,所以必须借助 eval 函数.
作为对比,在 nodejs 源码 中, wrap 是这样实现的
最终对该 warpper 的解析实现在
Module.wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname) { ',
'\n});'
];
Module.prototype._compile
方法中,其中用到了 vm 模块对 wrapper 脚本进行处理.vm 实现的功能与 eval 函数类似,但比 eval 函数更强大.
模块引用 require()
对模块的引用我们通过 require(..) 函数进行引用,如
var http = require('http')
,该方法简单实现如下:
定义了一个 module 对象用来保存通过 loadModule 方法中加载模块中暴露出的接口.
const require = (moduleName) => {
console.log(`Require invoked for module: ${moduleName}`);
const id = require.resolve(moduleName);
if (require.cache[id]) { return require.cache[id].exports; }
// 1.module metadata
const module = {
exports: {},
id: id
}
// 2.require.cache
require.cache[id] = module;
// 3.load the module
loadModule(id, module, require);
// 4.return exported variables
return module.exports;
}
require.cache = {};
require.resolve = (moduleName) => {
/* resolve a full module id from the moduleName */
}
将第一次加载的模块保存在内部缓存中.即第二次调用 require(..) 时不会再调用 loadModule 方法,直接从 cache 中返回.
通过 loadModule 方法通过模块路径加载模块内容.
返回模块中暴露的接口以供调用.
可以通过下图更加直观的了解其中的关系.
module.exports vs exports
通过上述代码和图示可知,我们写的模块中的 exports 其实是对 module.exports 的引用. 因此我们可以通过 exports 添加属性来给 module.exports 引用的对象添加属性.
exports.hello = () => { console.log('Hello') };
但如果给 exports 重新赋值,则会失去 module.exports 的引用
exports = () => { console.log('Hello') };
此时
exports !== module.exports
,意味着通过 exports 暴露的接口是无效的,没有添加到 module metadata 中的 exports 中.
来源: https://juejin.im/post/5a5b58f06fb9a01cb74e565c