Node.js design pattern一書中對Node的Module模塊機制這一塊,我以爲講的挺透徹和易懂,這裏根據本身理解作下總結。本文轉發自本人github。javascript
自定義一個簡單的模塊加載方法loadModule
,基本思路跟nodejs一致,將加載的模塊內容包裹在一個函數裏面實現變量的隔離,保證模塊內的變量都是私有的。java
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
函數。node
做爲對比,在nodejs源碼中, wrap
是這樣實現的git
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
函數更強大。github
對模塊的引用咱們經過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
方法經過模塊路徑加載模塊內容。能夠經過下圖更加直觀的瞭解其中的關係。app
module.exports vs exports函數
經過上述代碼和圖示可知,咱們寫的模塊中的exports
實際上是對module.exports
的引用。 所以咱們能夠經過exports
添加屬性來給module.exports
引用的對象添加屬性。ui
exports.hello = () => { console.log('Hello') };
複製代碼
但若是給exports
從新賦值,則會失去module.exports
的引用spa
exports = () => { console.log('Hello') };
複製代碼
此時exports !== module.exports
,意味着經過exports
暴露的接口是無效的,沒有添加到module metadata中的exports
中。