大部分實作在module.js中,按照順序來看: 首先調用require('xx')的時候内部調用了Module._load(path, parent) :
- Module.prototype.require = function(path) {
- assert(path, 'missing path');
- assert(util.isString(path), 'path must be a string');
- return Module._load(path, this);
- };
至于我們平時調用的require方法是不是就是這個Module原型上的require在稍後就可以确認。
_load函數裡處理了子產品的緩存邏輯,這個大家知道下就行,接下來主要是這段邏輯
- var module = new Module(filename, parent);
-
- if (isMain) {
- process.mainModule = module;
- module.id = '.';
- }
-
- Module._cache[filename] = module;
-
- var hadException = true;
-
- try {
- module.load(filename);
- hadException = false;
- } finally {
- if (hadException) {
- delete Module._cache[filename];
- }
- }
- return module.exports;
可以看到try塊中調用了module.load(file)方法,然後調用完就傳回了module對象的exports屬性,那繼續檢視load(file)方法是怎麼加載這個file并且指派給module.exports。
- Module.prototype.load = function(filename) {
- debug('load ' + JSON.stringify(filename) +
- ' for module ' + JSON.stringify(this.id));
-
- assert(!this.loaded);
- this.filename = filename;
- this.paths = Module._nodeModulePaths(path.dirname(filename));
-
- var extension = path.extname(filename) || '.js';
- if (!Module._extensions[extension]) extension = '.js';
- Module._extensions[extension](this, filename);
- this.loaded = true;
- };
直接看倒數4行,這幾行就是根據要require的檔案字尾來進行不同方法分發,我們這裡就看最常見的對js檔案的處理邏輯,其他字尾還支援'.node','.json'等。
- // Native extension for .js
- Module._extensions['.js'] = function(module, filename) {
- var content = fs.readFileSync(filename, 'utf8');
- module._compile(stripBOM(content), filename);
- };
對.js檔案,就是直接讀取了這個檔案的文本内容做一些字元處理,然後調用_compile 方法,好了,馬上就到終點了,直接看_compile(content,filename)方法。
_compile方法内容較長,但大部分不用特别深究,不影響了解require的邏輯。大緻做了以下事情: 建構一個require變量
- Module.prototype._compile = function(content, filename) {
- var self = this;
- // remove shebang
- content = content.replace(/^\#\!.*/, '');
-
- function require(path) {
- return self.require(path);
- }
-
- require.resolve = function(request) {
- return Module._resolveFilename(request, self);
- };
-
- Object.defineProperty(require, 'paths', { get: function() {
- throw new Error('require.paths is removed. Use ' +
- 'node_modules folders, or the NODE_PATH ' +
- 'environment variable instead.');
- }});
-
- require.main = process.mainModule;
-
- // Enable support to add extra extension types
- require.extensions = Module._extensions;
- require.registerExtension = function() {
- throw new Error('require.registerExtension() removed. Use ' +
- 'require.extensions instead.');
- };
-
- require.cache = Module._cache;
還記得一開始的問題麼,這個require對象就是Module.require,其實這個require對象就是我們平時調用的require('xxx')所用的那個。
然後有一段根據配置決定子產品加載方式的,可以先不用看,因為預設不走這段邏輯,未避免分散注意力,這裡就不貼了,有興趣可以自己看環境變量:NODE_MODULE_CONTEXTS。
接下來才終于到了真正要進行動手裝載子產品的時候了,沒幾行代碼:
- var wrapper = Module.wrap(content);
這個wrap方法調用的是NativeModule.wrap方法,至于NativeModule.wrap方法究竟是幹嘛的可看node.js源碼:
- NativeModule.wrap = function(script) {
- return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
- };
-
- NativeModule.wrapper = [
- '(function (exports, require, module, __filename, __dirname) { ',
- '\n});'
- ];
這個wrap方法非常重要!,直接解釋了require核心是怎麼實作的,其實就是把require的那個js檔案的代碼文本包在一個匿名函數體裡。再看下這個自匿名函數的參數,也就是每個js子產品中能直接通路到的變量。
接下去看:
- var compiledWrapper = runInThisContext(wrapper, { filename: filename });
這裡調用了vm子產品的runInThisContext,注意,這裡并沒有執行子產品的代碼哦,這裡隻是執行了wrap方法,這句運作完其實隻是獲得了一個匿名函數對象(之前隻是代碼文本)。
接下去才是真正執行這個匿名函數。
- var args = [self.exports, require, self, filename, dirname];
- return compiledWrapper.apply(self.exports, args);
對應下這裡的參數,這裡可以很簡單發現,首先每個子產品的this就是module.exports(apply的第一個參數), 其次exports就是module對象上的exports,同一個東西。是以這裡可以直接弄明白exports和module的關系了,可以直接在exports上加東西,或者直接換一個。然後傳入的require就是_compile方法剛開始建立的那個require方法,執行邏輯是一緻的,是以提供了子產品加載的傳遞性。 是以在每個子產品中直接去修改module對象上的exports就決定了最終子產品傳回的結果。
總結下,上面介紹的過程主要是最常見的加載非本地js子產品的邏輯流程。根據代碼可以知道每個子產品中可以通路到的5個變量。了解了這些後對node中基本的module加載應該沒不存在其他很大的疑惑了。