天天看點

nodejs module.export require 原理分析

   大部分實作在module.js中,按照順序來看: 首先調用require('xx')的時候内部調用了Module._load(path, parent) :

  1. Module.prototype.require = function(path) {
  2. assert(path, 'missing path');
  3. assert(util.isString(path), 'path must be a string');
  4. return Module._load(path, this);
  5. };

       至于我們平時調用的require方法是不是就是這個Module原型上的require在稍後就可以确認。 

      _load函數裡處理了子產品的緩存邏輯,這個大家知道下就行,接下來主要是這段邏輯

  1. var module = new Module(filename, parent);
  2. if (isMain) {
  3. process.mainModule = module;
  4. module.id = '.';
  5. }
  6. Module._cache[filename] = module;
  7. var hadException = true;
  8. try {
  9. module.load(filename);
  10. hadException = false;
  11. } finally {
  12. if (hadException) {
  13. delete Module._cache[filename];
  14. }
  15. }
  16. return module.exports;

    可以看到try塊中調用了module.load(file)方法,然後調用完就傳回了module對象的exports屬性,那繼續檢視load(file)方法是怎麼加載這個file并且指派給module.exports。

  1. Module.prototype.load = function(filename) {
  2. debug('load ' + JSON.stringify(filename) +
  3. ' for module ' + JSON.stringify(this.id));
  4. assert(!this.loaded);
  5. this.filename = filename;
  6. this.paths = Module._nodeModulePaths(path.dirname(filename));
  7. var extension = path.extname(filename) || '.js';
  8. if (!Module._extensions[extension]) extension = '.js';
  9. Module._extensions[extension](this, filename);
  10. this.loaded = true;
  11. };

     直接看倒數4行,這幾行就是根據要require的檔案字尾來進行不同方法分發,我們這裡就看最常見的對js檔案的處理邏輯,其他字尾還支援'.node','.json'等。   

  1. // Native extension for .js
  2. Module._extensions['.js'] = function(module, filename) {
  3. var content = fs.readFileSync(filename, 'utf8');
  4. module._compile(stripBOM(content), filename);
  5. };

    對.js檔案,就是直接讀取了這個檔案的文本内容做一些字元處理,然後調用_compile 方法,好了,馬上就到終點了,直接看_compile(content,filename)方法。

     _compile方法内容較長,但大部分不用特别深究,不影響了解require的邏輯。大緻做了以下事情: 建構一個require變量

  1. Module.prototype._compile = function(content, filename) {
  2. var self = this;
  3. // remove shebang
  4. content = content.replace(/^\#\!.*/, '');
  5. function require(path) {
  6. return self.require(path);
  7. }
  8. require.resolve = function(request) {
  9. return Module._resolveFilename(request, self);
  10. };
  11. Object.defineProperty(require, 'paths', { get: function() {
  12. throw new Error('require.paths is removed. Use ' +
  13. 'node_modules folders, or the NODE_PATH ' +
  14. 'environment variable instead.');
  15. }});
  16. require.main = process.mainModule;
  17. // Enable support to add extra extension types
  18. require.extensions = Module._extensions;
  19. require.registerExtension = function() {
  20. throw new Error('require.registerExtension() removed. Use ' +
  21. 'require.extensions instead.');
  22. };
  23. require.cache = Module._cache;

    還記得一開始的問題麼,這個require對象就是Module.require,其實這個require對象就是我們平時調用的require('xxx')所用的那個。

      然後有一段根據配置決定子產品加載方式的,可以先不用看,因為預設不走這段邏輯,未避免分散注意力,這裡就不貼了,有興趣可以自己看環境變量:NODE_MODULE_CONTEXTS。

       接下來才終于到了真正要進行動手裝載子產品的時候了,沒幾行代碼:

  1. var wrapper = Module.wrap(content);

    這個wrap方法調用的是NativeModule.wrap方法,至于NativeModule.wrap方法究竟是幹嘛的可看node.js源碼:     

  1. NativeModule.wrap = function(script) {
  2. return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  3. };
  4. NativeModule.wrapper = [
  5. '(function (exports, require, module, __filename, __dirname) { ',
  6. '\n});'
  7. ];

      這個wrap方法非常重要!,直接解釋了require核心是怎麼實作的,其實就是把require的那個js檔案的代碼文本包在一個匿名函數體裡。再看下這個自匿名函數的參數,也就是每個js子產品中能直接通路到的變量。

       接下去看:

  1. var compiledWrapper = runInThisContext(wrapper, { filename: filename });

      這裡調用了vm子產品的runInThisContext,注意,這裡并沒有執行子產品的代碼哦,這裡隻是執行了wrap方法,這句運作完其實隻是獲得了一個匿名函數對象(之前隻是代碼文本)。

       接下去才是真正執行這個匿名函數。

  1. var args = [self.exports, require, self, filename, dirname];
  2. 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加載應該沒不存在其他很大的疑惑了。