Node.js design pattern一书中对Node的Module模块机制这一块,我觉得讲的挺透彻和易懂,这里根据自己理解做下总结。本文转发自本人 github 。
loadModule
自定义一个简单的模块加载方法 loadModule
,基本思路跟nodejs一致,将加载的模块内容包裹在一个函数里面实现变量的隔离,保证模块内的变量都是私有的。
function loadModule(filename, module, require) { const wrappedSrc = `(function(module, exports, require) { ${fs.readFileSync(filename, 'utf8')} })(module, module.exports, require);`; eval(wrappedSrc); }
这个例子通过 eval
对 wrappedSrc
进行计算,即通过 eval
函数处理该字符串脚本。因为这里要把 (function(module, exports, require) {
这串东西和 fs.readFileSync(filename, 'utf8')
加载的模块内容合并在一起作为新的整合代码再运行,所以必须借助 eval
函数。
作为对比,在 nodejs源码 中, wrap
是这样实现的
Module.wrap = function(script) { return Module.wrapper[0] + script + Module.wrapper[1]; }; Module.wrapper = [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ];
最终对该 warpper
的解析实现在 Module.prototype._compile
方法中,其中用到了 vm
模块对 wrapper
脚本进行处理。 vm
实现的功能与 eval
函数类似,但比 eval
函数更强大。
模块引用require()
对模块的引用我们通过 require(..)
函数进行引用,如 var http = require('http')
,该方法简单实现如下:
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 */ }
- 定义了一个
module
对象用来保存通过loadModule
方法中加载模块中暴露出的接口。 - 将第一次加载的模块保存在内部缓存中。即第二次调用
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
中。
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。