[聚合文章] Node.js design pattern : module

JavaScript 2018-01-15 18 阅读

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);
	}

这个例子通过 evalwrappedSrc 进行计算,即通过 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 */
	}
  1. 定义了一个 module 对象用来保存通过 loadModule 方法中加载模块中暴露出的接口。
  2. 将第一次加载的模块保存在内部缓存中。即第二次调用 require(..) 时不会再调用 loadModule 方法,直接从 cache 中返回。
  3. 通过 loadModule 方法通过模块路径加载模块内容。
  4. 返回模块中暴露的接口以供调用。

可以通过下图更加直观的了解其中的关系。

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 中。

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。