首先說一下CommonJS 模塊和ES6模塊兩者的區別,這裏就直接先直接給出兩者的差別。html
首先是nodejs的模塊封裝器node
(function(exports, require, module, __filename, __dirname) { // 模塊的代碼實際上在這裏 });
如下是node的Module的源碼git
先看一下咱們require一個文件會作什麼github
Module.prototype.require = function(id) { validateString(id, 'id'); requireDepth++; try { return Module._load(id, this, /* isMain */ false); } finally { requireDepth--; } };
走到這裏至少就佐證了CommonJS 模塊是運行時加載,由於require實際就是module這個對象的一個方法,因此require一個js的模塊,必須是得在運行到某個module的require代碼時才能去加載另外一個文件。api
而後這裏指向了_load方法 這裏有個細節就是isMain這個的話其實就是node去區分加載模塊是不是主模塊的,由於是require的因此必然不該該是一個主模塊,當時循環引用的場景除外。閉包
接下來就找到_load方法app
Module._load = function(request, parent, isMain) { let relResolveCacheIdentifier; const cachedModule = Module._cache[filename]; if (cachedModule !== undefined) { updateChildren(parent, cachedModule, true); return cachedModule.exports; } const mod = loadNativeModule(filename, request, experimentalModules); if (mod && mod.canBeRequiredByUsers) return mod.exports; // Don't call updateChildren(), Module constructor already does. const module = new Module(filename, parent); if (isMain) { process.mainModule = module; module.id = '.'; } Module._cache[filename] = module; if (parent !== undefined) { relativeResolveCache[relResolveCacheIdentifier] = filename; } let threw = true; try { module.load(filename); threw = false; } finally { if (threw) { delete Module._cache[filename]; if (parent !== undefined) { delete relativeResolveCache[relResolveCacheIdentifier]; } } } return module.exports; };
首先看一下cache,它其實是處理了屢次require的狀況,從源碼中能夠發現屢次require一個模塊,node永遠的使用了第一次的module對象,並未作更新)。cache的細節其實和node模塊輸出的變量爲何不能在運行時被改變也是有關係的。由於就算運行中去改變某個模塊輸出的變量,而後在另外一個地方再次require,但是此時module.exports因爲有cache,因此並不會發生變化。可是這裏還不能說明CommonJS 模塊輸出的是一個值的拷貝。模塊化
接着來看new Module(filename, parent)實例化後運行的module.load
核心咱們關注的代碼就是函數
Module._extensions[extension](this, filename);
這裏是加載的代碼,而後咱們看一下js文件的加載ui
Module._extensions['.js'] = function(module, filename) { 。。。 const content = fs.readFileSync(filename, 'utf8'); module._compile(content, filename); };
這裏就是咱們編寫的js文件被加載的過程。
如下的代碼通過大量刪減
Module.prototype._compile = function(content, filename) { const compiledWrapper = wrapSafe(filename, content, this); const dirname = path.dirname(filename); const require = makeRequireFunction(this, redirects); var result; const exports = this.exports; const thisValue = exports; const module = this; result = compiledWrapper.call(thisValue, exports, require, module, filename, dirname); return result; };
首先
const require = makeRequireFunction(this, redirects);
這個是代碼中實際require關鍵詞是如何工做的關鍵。沒有什麼複雜的,主要是如何針對入參去找文件的,這裏就跳過了詳細的建議看一下node的官方文檔。這裏就是’CommonJS 模塊是運行時加載‘的鐵證,由於require其實都依賴於node模塊的執行時的注入,內部require的module更加須要在運行時纔會被compile了。
另外注意到this.exports做爲參數傳遞到了wrapSafe中,而整個執行做用域鎖定在了this.exports這個對象上。這裏是’CommonJS 模塊的頂層this指向當前模塊‘這句話的來源。
再看一下核心的模塊造成的函數wrapSafe
function wrapSafe(filename, content, cjsModuleInstance) { ... let compiled; try { compiled = compileFunction( content, filename, 0, 0, undefined, false, undefined, [], [ 'exports', 'require', 'module', '__filename', '__dirname', ] ); } catch (err) { ... } return compiled.function; }
核心代碼能夠說很是少,也就是一個閉包的構造器。也就是文章開頭提到的模塊封裝器。
compileFunction。這個接口能夠看node對應的[api](http://nodejs.cn/api/vm.html#...
)。
再看一個node官方對require的整個的簡化版
function require(/* ... */) { //對應new Module中 this.export = {} const module = { exports: {} }; //這裏的代碼就是對應了_load裏的module.load() ((module, exports) => { // Module code here. In this example, define a function. function someFunc() {} exports = someFunc; // At this point, exports is no longer a shortcut to module.exports, and // this module will still export an empty default object. module.exports = someFunc; // At this point, the module will now export someFunc, instead of the // default object. })(module, module.exports); //注意看_load最後的輸出 return module.exports; }
這個時候再比較一下_load的代碼是否是恍然大悟。
最後就是’CommonJS 模塊輸出的是一個值的拷貝‘的解釋了,在cache的機制中已經說明了爲何重複require永遠不會重複執行,而在上面的函數中能夠看到咱們使用的exports中的值的拷貝。到這裏整個node的模塊化的特色也就都有很明確的解釋了。