Node.js design pattern : module

鈞嘢嘢發表於2019-03-04

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) { `,
	  `
});`
	];
複製程式碼

最終對該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. 返回模組中暴露的介面以供呼叫。

可以通過下圖更加直觀的瞭解其中的關係。

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

相關文章