angular源碼分析:angular的整個加載流程


在前面,我們講了angular的目錄結構、JQLite以及依賴注入的實現,在這一期中我們將重點分析angular的整個框架的加載流程。

一、從源代碼的編譯順序開始

下面是我們在目錄結構哪一期理出的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的各種服務的目錄,該目錄下一個文件按名字對應一個服務  
│    │
│    ├─── *.js        //各種服務的定義
│    ├─── filter.js     //定義過濾器,注冊具體的過濾器
│    ├─── filter         //過濾器目錄, 
│    │     │
│    │     └─── *.js   //過濾器的具體實現
│    └─── directive      //指令目錄,該目錄下一個文件對應一個angular指令 
│          │ 
│          └─── *.js     //指令的具體實現
├─── angular.bind.js           //簡單幾行代碼,判斷是否已經加載了jQuery,如果是,直接使用jQuery,而不使用jqLite  
├─── publishExternalApis.js        // `publishExternalAPI(angular);`導出變量和接口  
│  
└── angular.suffix   //util.wrap函數加入的后綴代碼

二、找到代碼的入口點

  //try to bind to jquery now so that one can write angular.element().read()
  //but we will rebind on bootstrap again.
  bindJQuery();  //綁定jquery:如果系統已經加載了jQuery,綁定使用,如果沒有則是用angular自身的jqLite

  publishExternalAPI(angular); //導出angular的對外公開的函數和屬性

  jqLite(document).ready(function() { //等待dom加載完后啟動angular
    angularInit(document, bootstrap);
  });

三、dom加載前的准備工作

1.bindJQuery

這里將bindJQuery的代碼貼出來,看看

function bindJQuery() {
  // bind to jQuery if present;
  jQuery = window.jQuery;
  // reset to jQuery or default to us.
  if (jQuery) { //如果使用jQuery,對其做了些擴展處理
    jqLite = jQuery;
    extend(jQuery.fn, {
      scope: JQLitePrototype.scope,
      isolateScope: JQLitePrototype.isolateScope,
      controller: JQLitePrototype.controller,
      injector: JQLitePrototype.injector,
      inheritedData: JQLitePrototype.inheritedData
    });

     //下面是對jquery改變dom時,增加一些清理工作,包括remove(刪除元素),empty(置空),html(重寫元素內容時)
    // Method signature:
    //     jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
    jqLitePatchJQueryRemove('remove', true, true, false);
    jqLitePatchJQueryRemove('empty', false, false, false);
    jqLitePatchJQueryRemove('html', false, false, true);
  } else {
    jqLite = JQLite;
  }
  angular.element = jqLite;
}

2.publishExternalAPI

下面把publishExternalAPI的代碼也貼出來

function publishExternalAPI(angular){
  extend(angular, {  //導出工具函數,就是直接將要導出的函數和屬性加到angular對象上。細心的同學會發現下面少了angular.module的module。
    'bootstrap': bootstrap,
    'copy': copy,
    'extend': extend,
    'equals': equals,
    'element': jqLite,
    'forEach': forEach,
    'injector': createInjector,
    'noop':noop,
    'bind':bind,
    'toJson': toJson,
    'fromJson': fromJson,
    'identity':identity,
    'isUndefined': isUndefined,
    'isDefined': isDefined,
    'isString': isString,
    'isFunction': isFunction,
    'isObject': isObject,
    'isNumber': isNumber,
    'isElement': isElement,
    'isArray': isArray,
    'version': version,
    'isDate': isDate,
    'lowercase': lowercase,
    'uppercase': uppercase,
    'callbacks': {counter: 0},
    '$$minErr': minErr,
    '$$csp': csp
  });

  angularModule = setupModuleLoader(window); //這里需要重點分析
  try {
    angularModule('ngLocale');
  } catch (e) {
    angularModule('ngLocale', [])
    .provider('$locale', $LocaleProvider);
  }

  angularModule('ng', ['ngLocale'], ['$provide', //定義“ng”模塊,$provide作為后面函數的依賴注入對象,可見$provide是在之前的模塊中定義的
    function ngModule($provide) {

      // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
      $provide.provider({ //注冊$$sanitizeUri服務
        $$sanitizeUri: $$SanitizeUriProvider
      });

      $provide
      .provider('$compile', $CompileProvider) //定義$compile服務
      .directive({ //注冊各種指令
            a: htmlAnchorDirective,
            input: inputDirective,
            textarea: inputDirective,
            form: formDirective,
            script: scriptDirective,
            select: selectDirective,
            style: styleDirective,
            option: optionDirective,
            ngBind: ngBindDirective,
            ngBindHtml: ngBindHtmlDirective,
            ngBindTemplate: ngBindTemplateDirective,
            ngClass: ngClassDirective,
            ngClassEven: ngClassEvenDirective,
            ngClassOdd: ngClassOddDirective,
            ngCloak: ngCloakDirective,
            ngController: ngControllerDirective,
            ngForm: ngFormDirective,
            ngHide: ngHideDirective,
            ngIf: ngIfDirective,
            ngInclude: ngIncludeDirective,
            ngInit: ngInitDirective,
            ngNonBindable: ngNonBindableDirective,
            ngPluralize: ngPluralizeDirective,
            ngRepeat: ngRepeatDirective,
            ngShow: ngShowDirective,
            ngStyle: ngStyleDirective,
            ngSwitch: ngSwitchDirective,
            ngSwitchWhen: ngSwitchWhenDirective,
            ngSwitchDefault: ngSwitchDefaultDirective,
            ngOptions: ngOptionsDirective,
            ngTransclude: ngTranscludeDirective,
            ngModel: ngModelDirective,
            ngList: ngListDirective,
            ngChange: ngChangeDirective,
            required: requiredDirective,
            ngRequired: requiredDirective,
            ngValue: ngValueDirective
        })
        .directive({ //注冊ngInclude 指令
          ngInclude: ngIncludeFillContentDirective
        })
        .directive(ngAttributeAliasDirectives) //?
        .directive(ngEventDirectives);//?

      $provide.provider({ //注冊剩余的各種服務
        $anchorScroll: $AnchorScrollProvider,
        $animate: $AnimateProvider,
        $browser: $BrowserProvider,
        $cacheFactory: $CacheFactoryProvider,
        $controller: $ControllerProvider,
        $document: $DocumentProvider,
        $exceptionHandler: $ExceptionHandlerProvider,
        $filter: $FilterProvider,
        $interpolate: $InterpolateProvider,
        $interval: $IntervalProvider,
        $http: $HttpProvider,
        $httpBackend: $HttpBackendProvider,
        $location: $LocationProvider,
        $log: $LogProvider,
        $parse: $ParseProvider,
        $rootScope: $RootScopeProvider,
        $q: $QProvider,
        $sce: $SceProvider,
        $sceDelegate: $SceDelegateProvider,
        $sniffer: $SnifferProvider,
        $templateCache: $TemplateCacheProvider,
        $timeout: $TimeoutProvider,
        $window: $WindowProvider
      });
    }
  ]);
}

細心的同學會發現:在導出的工具函數數組中少了angular.module的module,先不管,往下看。
我在這里重點分析setupModuleLoader函數以及angularModule對象到底是啥

function setupModuleLoader(window) {

  var $injectorMinErr = minErr('$injector');
  var ngMinErr = minErr('ng');

  function ensure(obj, name, factory) {
    return obj[name] || (obj[name] = factory());
  }

  var angular = ensure(window, 'angular', Object);

  angular.$$minErr = angular.$$minErr || minErr;

  return ensure(angular, 'module', function() {
    var modules = {};

    return function module(name, requires, configFn) {...};
  });
}

ensure(obj, name, factory)函數的含義:如果obj對象上存在name屬性,直接返回;如果不存在,通過factory進行構造。
那么,angularModule = setupModuleLoader(window)執行后,會在angular上增加一個module屬性,並且返回給angularModule。這里就把前面缺少的angular.module給加上了。

下面是module(name, requires, configFn)的具體代碼實現(為節約篇幅,省去注釋):

function module(name, requires, configFn) {
  var assertNotHasOwnProperty = function(name, context) {
    if (name === 'hasOwnProperty') {
      throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
    }
  };

  assertNotHasOwnProperty(name, 'module');
  if (requires && modules.hasOwnProperty(name)) {
    modules[name] = null;
  }
  return ensure(modules, name, function() {
    if (!requires) {
      throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
         "the module name or forgot to load it. If registering a module ensure that you " +
         "specify the dependencies as the second argument.", name);
    }  
    var invokeQueue = [];  
    var runBlocks = [];
    var config = invokeLater('$injector', 'invoke');  
    var moduleInstance = {
      // Private state
      _invokeQueue: invokeQueue,
      _runBlocks: runBlocks,    
      requires: requires,   //依賴模塊
      name: name,    
      provider: invokeLater('$provide', 'provider'),    
      factory: invokeLater('$provide', 'factory'),    
      service: invokeLater('$provide', 'service'),    
      value: invokeLater('$provide', 'value'),    
      constant: invokeLater('$provide', 'constant', 'unshift'),    
      animation: invokeLater('$animateProvider', 'register'),    
      filter: invokeLater('$filterProvider', 'register'),    
      controller: invokeLater('$controllerProvider', 'register'),    
      directive: invokeLater('$compileProvider', 'directive'),    
      config: config,    
      run: function(block) {
        runBlocks.push(block);
        return this;
      }
    };

    if (configFn) {
      config(configFn);
    }
    return  moduleInstance;  
    function invokeLater(provider, method, insertMethod) {
      return function() {
        invokeQueue[insertMethod || 'push']([provider, method, arguments]);
        return moduleInstance;
      };
    }
  });
}

分析invokeLater函數:這個函數的功能是返回一個函數,這個函數能向invokeQueue數組中插入一個三元組(provider, method, arguments)
那么,就是說provider、factory等函數的功能都是向_invokeQueue壓入數據
`module().run(xx_function)將把xx_function壓入_runBlocks隊列數組中。
問題來了:這些將在哪里得到“執行”呢?
結合上一期留的坑:

runInvokeQueue(moduleFn._invokeQueue); //moduleFn._invokeQueue是什么鬼,先留坑在此
runInvokeQueue(moduleFn._configBlocks); //moduleFn._configBlocks是什么鬼,也留坑在此

就很容易想到了:他們將在createInjector中得到執行。那么,createInjector本身又在哪里執行呢?

##四、dom加載后的工作:`angularInit(document, bootstrap)`

###1.angularInit的源碼:
```js
function angularInit(element, bootstrap) {
  var elements = [element],
      appElement,
      module,
      names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
      NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;

  function append(element) {
    element && elements.push(element);
  }

  forEach(names, function(name) {
    names[name] = true;
    append(document.getElementById(name));
    name = name.replace(':', '\\:');
    if (element.querySelectorAll) {
      forEach(element.querySelectorAll('.' + name), append);
      forEach(element.querySelectorAll('.' + name + '\\:'), append);
      forEach(element.querySelectorAll('[' + name + ']'), append);
    }
  });

  forEach(elements, function(element) {
    if (!appElement) {
      var className = ' ' + element.className + ' ';
      var match = NG_APP_CLASS_REGEXP.exec(className);
      if (match) {
        appElement = element;
        module = (match[2] || '').replace(/\s+/g, ',');
      } else {
        forEach(element.attributes, function(attr) {
          if (!appElement && names[attr.name]) {
            appElement = element;
            module = attr.value;
          }
        });
      }
    }
  });
  if (appElement) {
    bootstrap(appElement, module ? [module] : []);
  }
}

上面的代碼寫了很多,但是只干了兩件事:1.在dom中尋找啟動節點,讀出啟動節點定義的啟動模塊的名字;2.如果找到了啟動節點,用啟動節點和其定義的啟動模塊的名字為參數調用bootstrap。

2.啟動器bootstrap

function bootstrap(element, modules) {
  var doBootstrap = function() {
    element = jqLite(element);

    if (element.injector()) {
      var tag = (element[0] === document) ? 'document' : startingTag(element);
      throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
    }

    modules = modules || [];
    modules.unshift(['$provide', function($provide) { //將['$provide',function(){...}]壓入模塊數組
      $provide.value('$rootElement', element);
    }]);
    modules.unshift('ng'); //將ng壓入模塊數組
    var injector = createInjector(modules); //創建注入器
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',//注入根作用域、根元素、編譯器、注入器、動畫
       function(scope, element, compile, injector, animate) { 
        scope.$apply(function() { //在根作用域上啟動事件循環
          element.data('$injector', injector); //在根元素上保存注入器
          compile(element)(scope); //編譯根元素
        });
      }]
    );
    return injector;
  };

  var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;

  if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
    return doBootstrap();
  }

  window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
  angular.resumeBootstrap = function(extraModules) {
    forEach(extraModules, function(module) {
      modules.push(module);
    });
    doBootstrap();
  };
}

好了,現在是時候看看上一期的內容了
上一期:angular源碼分析:injector.js文件分析——angular中的依賴注入式如何實現的(續)
下一期:angular源碼分析:圖解angular的啟動流程
ps:下一期中,我將畫一些圖來總結前面所講的內容,希望對大家有幫助


免責聲明!

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



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