Node.js design pattern : module

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. 返回模块中暴露的接口以供调用。

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

《Node.js design pattern : module》

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
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞