angular源碼分析:angular的源代碼目錄結構說明


一、讀源碼,是選擇“編譯合並后”的呢還是“編譯前的”呢?

有朋友說,讀angular源碼,直接看編譯后的,多好,不用管模塊間的關系,從上往下讀就好了。但是在我看來,閱讀編譯后的源碼至少有兩點不好。

  • 1.編譯已經將所有的代碼合並在一起了,這會丟失掉作者模塊設計的思想,不利於理解代碼架構的精髓,甚至想理解代碼都很難。
  • 2.編譯合並后的angular代碼有2萬多行,想要看完,早已望而生畏。相反閱讀編譯前的代碼,可以一個模塊一個模塊的逐個擊破。
    但是編譯前的源碼中的確文件一大堆,一開始靠不清楚都是拿來干嘛的,這可能是初學者源代碼閱讀一個很大的障礙。我今天要做的事就是把angular的angular的目錄和文件的結構理清分享給大家。

二、Gruntfile.js

1.angular的編譯合並以及測試使用的是grunt框架,所以首先應該從這個文件着手。

查看其源代碼。

var files = require('./angularFiles').files;
var util = require('./lib/grunt/utils.js');

注意:在grunt開頭引入了這個兩個文件:angularFiles.js 和 ./lib/grunt/utils.js。

2.angularFiles.js:

var angularFiles = {
    'angularSrc': [...],

    'angularLoader': [...],

    'angularModules': {
    'ngAnimate': [...],
    'ngCookies': [...],
    'ngMessageFormat': [...],
    'ngMessages': [...],
    'ngResource': [...],
    'ngRoute': [...],
    'ngSanitize': [...],
    'ngMock': [...],
    'ngTouch': [...],
    'ngAria': [...]
  },

  'angularScenario': [...],

  'angularTest': [...],

  'karma': [...],

  'karmaExclude': [...],

  'karmaScenario': [...],

  "karmaModules": [...],

  'karmaJquery': [...],

  'karmaJqueryExclude': [...]
};

 ...
if (exports) {
  exports.files = angularFiles;
  exports.mergeFilesFor = function() {
    var files = [];

    Array.prototype.slice.call(arguments, 0).forEach(function(filegroup) {
      angularFiles[filegroup].forEach(function(file) {
        // replace @ref
        var match = file.match(/^\@(.*)/);
        if (match) {
          files = files.concat(angularFiles[match[1]]);
        } else {
          files.push(file);
        }
      });
    });

    return files;
  };
}

可以看到,其中就是定義了一個數組(files,其中每個元素都是一個文件名的數組。我們可以輕易的發現,files其實將所有的angular源碼給分組追蹤起來了)和一個函數(mergeFilesFor,用於合並angularFiles中的元素)。

3.將“/lib/grunt/utils.js”文件跳過,在Gruntfile.js中搜索 關鍵詞 build,可以看到如下代碼:

    build: {
      scenario: {...
      },
      angular: {
        dest: 'build/angular.js',
        src: util.wrap([files['angularSrc']], 'angular'),
        styles: {
          css: ['css/angular.css'],
          generateCspCssFile: true,
          minify: true
        }
      },
      loader: {...},
      touch: {...},
      mocks: {...},
      sanitize: {...},
      resource: {...},
      messageformat: {...},
      messages: {...},
      animate: {...},
      route: {...},
      cookies: {...},
      aria: {...},
      "promises-aplus-adapter": {...}
    },

我們可以猜測,"angular: {...}"就是對應angular.js的編譯配置,而"loader: {...}"對應的是angular-loader.js,"touch: {...}"對應的是angular-touch.js,……
那么,我們重點分析angular.js的編譯配置,其他的都是類似的。

      angular: {
        dest: 'build/angular.js',
        src: util.wrap([files['angularSrc']], 'angular'),
        styles: {
          css: ['css/angular.css'],
          generateCspCssFile: true,
          minify: true
        }
      },

4.util.wrap是什么鬼。從var util = require('./lib/grunt/utils.js');,我們知道應該閱讀./lib/grunt/utils.js。

果然其中有一個導出的函數wrap

  wrap: function(src, name){
    src.unshift('src/' + name + '.prefix');
    src.push('src/' + name + '.suffix');
    return src;
  },

另外還有一個build函數,主要功能是合並js代碼

  build: function(config, fn){
    var files = grunt.file.expand(config.src);
    var styles = config.styles;
    var processedStyles;
    //concat
    var src = files.map(function(filepath) {
      return grunt.file.read(filepath);
    }).join(grunt.util.normalizelf('\n'));
    //process
    var processed = this.process(src, grunt.config('NG_VERSION'), config.strict);
    if (styles) {
      processedStyles = this.addStyle(processed, styles.css, styles.minify);
      processed = processedStyles.js;
      if (config.styles.generateCspCssFile) {
        grunt.file.write(removeSuffix(config.dest) + '-csp.css', CSP_CSS_HEADER + processedStyles.css);
      }
    }
    //write
    grunt.file.write(config.dest, processed);
    grunt.log.ok('File ' + config.dest + ' created.');
    fn();

    function removeSuffix(fileName) {
      return fileName.replace(/\.js$/, '');
    }
  },

通過簡單推理,build函數對代碼的合並時根據配置數組的順序粘貼完成的,而angular所包含的文件如下:

  'angularSrc': [
    'src/angular.prefix',  //util.wrap函數加入的前綴代碼

    'src/minErr.js',
    'src/Angular.js',
    'src/loader.js',
    'src/stringify.js',
    'src/AngularPublic.js',
    'src/jqLite.js',
    'src/apis.js',

    'src/auto/injector.js',

    'src/ng/anchorScroll.js',
    'src/ng/animate.js',
    'src/ng/animateCss.js',
    'src/ng/browser.js',
    'src/ng/cacheFactory.js',
    'src/ng/compile.js',
    'src/ng/controller.js',
    'src/ng/document.js',
    'src/ng/exceptionHandler.js',
    'src/ng/forceReflow.js',
    'src/ng/http.js',
    'src/ng/httpBackend.js',
    'src/ng/interpolate.js',
    'src/ng/interval.js',
    'src/ng/locale.js',
    'src/ng/location.js',
    'src/ng/log.js',
    'src/ng/parse.js',
    'src/ng/q.js',
    'src/ng/raf.js',
    'src/ng/rootScope.js',
    'src/ng/sanitizeUri.js',
    'src/ng/sce.js',
    'src/ng/sniffer.js',
    'src/ng/templateRequest.js',
    'src/ng/testability.js',
    'src/ng/timeout.js',
    'src/ng/urlUtils.js',
    'src/ng/window.js',
    'src/ng/cookieReader.js',

    'src/ng/filter.js',
    'src/ng/filter/filter.js',
    'src/ng/filter/filters.js',
    'src/ng/filter/limitTo.js',
    'src/ng/filter/orderBy.js',

    'src/ng/directive/directives.js',
    'src/ng/directive/a.js',
    'src/ng/directive/attrs.js',
    'src/ng/directive/form.js',
    'src/ng/directive/input.js',
    'src/ng/directive/ngBind.js',
    'src/ng/directive/ngChange.js',
    'src/ng/directive/ngClass.js',
    'src/ng/directive/ngCloak.js',
    'src/ng/directive/ngController.js',
    'src/ng/directive/ngCsp.js',
    'src/ng/directive/ngEventDirs.js',
    'src/ng/directive/ngIf.js',
    'src/ng/directive/ngInclude.js',
    'src/ng/directive/ngInit.js',
    'src/ng/directive/ngList.js',
    'src/ng/directive/ngModel.js',
    'src/ng/directive/ngNonBindable.js',
    'src/ng/directive/ngOptions.js',
    'src/ng/directive/ngPluralize.js',
    'src/ng/directive/ngRepeat.js',
    'src/ng/directive/ngShowHide.js',
    'src/ng/directive/ngStyle.js',
    'src/ng/directive/ngSwitch.js',
    'src/ng/directive/ngTransclude.js',
    'src/ng/directive/script.js',
    'src/ng/directive/select.js',
    'src/ng/directive/style.js',
    'src/ng/directive/validators.js',
    'src/angular.bind.js',
    'src/publishExternalApis.js',
    'src/ngLocale/angular-locale_en-us.js',

    'src/angular.suffix'   //util.wrap函數加入的后綴代碼
  ],

5.分析下angular.prefix 和 angular.suffix

angular.prefix

/**
 * @license AngularJS v"NG_VERSION_FULL"
 * (c) 2010-2015 Google, Inc. http://angularjs.org
 * License: MIT
 */
(function(window, document, undefined) {

angular.suffix

  jqLite(document).ready(function() {
    angularInit(document, bootstrap);
  });

})(window, document);

很熟悉吧,angular.prefix 和 angular.suffix 剛好構成一個自動執行的匿名函數。在這個函數的最后做了一個操作,等待jqLite准備好后,執行angularInit(document, bootstrap);,如果我們今天是分析angular的代碼可能就會繼續去追jqLite函數以及angularInit函數。但是今天不是,我們的目標是樹立代碼目錄結構。其實目錄結構已經出來了。

三、angular的代碼目錄結構:

├─── angular.prefix   //util.wrap函數加入的前綴代碼  
│  
├─── minErr.js       //錯誤處理  
├─── Angular.js      //主要定義angular的工具函數,我們[上一期]主要講了這個  
├─── loader.js       //定義了setupModuleLoader函數  
├─── stringify.js    //定義了對象序列化serializeObject,和對象調試輸出字符串serializeObject  
├─── AngularPublic.js //定義了angular導出的函數和變量  
├─── jqLite.js        //定義jqLite,一個mini的jQuery  
├─── apis.js          //定義了關於對象hash值的幾個函數  
├─── auto   
│     └─── injector.js //依賴注入和模塊加載,主要在這里實現,我在[第二期]講過部分  
│  
├─── ng                //定義angular的各種服務的目錄,該目錄下一個文件按名字對應一個服務  
│    ├─── anchorScroll.js  
│    ├─── animate.js  
│    ├─── animateCss.js  
│    ├─── browser.js  
│    ├─── cacheFactory.js  
│    ├─── compile.js  
│    ├─── controller.js  
│    ├─── document.js  
│    ├─── exceptionHandler.js  
│    ├─── forceReflow.js  
│    ├─── http.js  
│    ├─── httpBackend.js  
│    ├─── interpolate.js  
│    ├─── interval.js  
│    ├─── locale.js  
│    ├─── location.js  
│    ├─── log.js  
│    ├─── parse.js  
│    ├─── q.js  
│    ├─── raf.js  
│    ├─── rootScope.js  
│    ├─── sanitizeUri.js  
│    ├─── sce.js  
│    ├─── sniffer.js  
│    ├─── templateRequest.js  
│    ├─── testability.js  
│    ├─── timeout.js  
│    ├─── urlUtils.js  
│    ├─── window.js  
│    ├─── cookieReader.js  
│    ├─── filter.js  
│    ├─── filter         //過濾器目錄,該目錄下一個文件對應一個過濾器  
│    │     ├─── filter.js  
│    │     ├─── filters.js  
│    │     ├─── limitTo.js  
│    │     └─── orderBy.js  
│    └─── directive      //指令目錄,該目錄下一個文件對應一個angular指令  
│          ├─── directives.js  
│          ├─── a.js  
│          ├─── attrs.js  
│          ├─── form.js  
│          ├─── input.js  
│          ├─── ngBind.js  
│          ├─── ngChange.js  
│          ├─── ngClass.js  
│          ├─── ngCloak.js  
│          ├─── ngController.js  
│          ├─── ngCsp.js  
│          ├─── ngEventDirs.js  
│          ├─── ngIf.js  
│          ├─── ngInclude.js  
│          ├─── ngInit.js  
│          ├─── ngList.js  
│          ├─── ngModel.js  
│          ├─── ngNonBindable.js  
│          ├─── ngOptions.js  
│          ├─── ngPluralize.js  
│          ├─── ngRepeat.js  
│          ├─── ngShowHide.js  
│          ├─── ngStyle.js  
│          ├─── ngSwitch.js  
│          ├─── ngTransclude.js  
│          ├─── script.js  
│          ├─── select.js  
│          ├─── style.js  
│          └─── validators.js  
├─── angular.bind.js           //簡單幾行代碼,判斷是否已經加載了jQuery,如果是,直接使用jQuery,而不使用jqLite  
├─── publishExternalApis.js        // `publishExternalAPI(angular);`導出變量和接口  
├─── ngLocale  
│       └───angular─locale_en─us.js //本地化處理,其中定義地區性的星期月份神馬的  
│  
└── angular.suffix   //util.wrap函數加入的后綴代碼

上一期:angular源碼分析:angular中各種常用函數,比較省代碼的各種小技巧
下一期:angular源碼分析:angular中jqLite的實現——你可以丟掉jQuery了
ps:在這《angular源碼分析:angular中jqLite的實現——你可以丟掉jQuery了》這一期中,我將講述筆者在其中遇到的一個坑。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM