angularjs源碼分析之:angularjs執行流程

先上個大圖,有個大概印象,注:angularjs的版本爲:1.2.1,通過bower install angularjs安裝的。

幾個重要方法

bindJQuery();

publishExternalAPI(angular);

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

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

20121行,bindJQuery,嘗試綁定jQuery對象,如果沒有則採用內置的jqLite

20123行,publishExternalAPI,初始化angular環境。

1820-1848行,把一些基礎api掛載到angular上,如:extend,forEach,isFunction等。

1850行,angularModule = setupModuleLoader(window);此方法爲模塊加載器,在angular上添加了module方法,最後返回的實質上是:

angular.module = function module(name,require,configFn);

當我們angular.module('myApp'),只傳一個參數,爲getter操作,返回moduleInstance


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;
    }
}
當我們angular.module('myApp',[]) 時返回對象moduleInstance

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;
    }
}

因爲這樣,我們纔可以鏈式操作 ,如: .factory().controller().

這裏需要重點提到兩個函數:ensure 和invokeLater。

通過ensure(obj,nam,factory)可以實現:先檢索obj.name,如果有就是getter操作,否則爲setter操作。

此機制完成了在windows對象上聲明angular屬性,在angular對象上聲明module屬性。

通過invokeLater可以返回一個閉包,當調用configprovider(即moduleInstance返回的那些api)調用時,實質上就是調用這個閉包,拿app.provider('myprovider',['$window',function($window){ //code}])舉例,此api調用後,會將:

('$provide','provider',['$window',function($window){}]) push進invokeQueue數組中,注意此處只是入隊操作,並未執行。在後面執行時,實際上市通過 第一個參數調用第二個參數方法名,把第三個參數當變量傳入,即:args[0][args[1]].apply(args[0],args[2]);具體後面會分析到。

注意:上面提到api,run比較特殊,只把block放入到runBlock中,並沒有放入invokeQueue中。

20125,domready後調用angularInit

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] : []);
  }
}

遍歷names,通過document.getElementById(name) 或者是querySelectorAll(name)檢索到 element後存入 elements數組中,最後獲取到appElement以及module。舉個例子:我們一般會在文檔開始的html標籤上寫 ng-app="myApp".通過以上方法,我們最後可以得到 名爲myApp的module,後調用bootstrap(appElement,[module]);

bootstrap中需要重點關注 doBootstrap方法

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 = ['myApp'];
    modules = modules || [];
    //添加$provide這個數組
    modules.unshift(['$provide', function($provide) {
      $provide.value('$rootElement', element);
    }]);
    //添加 ng這個 module ,注意:1857行  我們註冊過ng 這個module,並在1854行 我們註冊過 它的依賴模塊'ngLocale',
    //angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 我們註冊過ngLocale這個module
    modules.unshift('ng');
    //調用createInjector(module) 此時:module爲:
    //['ng',['$provide',function(){}],'myApp']  兩個type爲string,一個爲array
    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;
};

createInjector是重點,拿出來單獨分析

function createInjector(modulesToLoad) {
  var INSTANTIATING = {},
      providerSuffix = 'Provider',
      path = [],
      loadedModules = new HashMap(),
      providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      providerInjector = (providerCache.$injector =
          createInternalInjector(providerCache, function() {
            throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
          })),
      instanceCache = {},
      instanceInjector = (instanceCache.$injector =
          createInternalInjector(instanceCache, function(servicename) {
            var provider = providerInjector.get(servicename + providerSuffix);
            return instanceInjector.invoke(provider.$get, provider);
          }));


  forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });

  return instanceInjector;
  /**
  ...省略若干
  **/

主要是四個變量:

  • providerCache
  • providerInjector
  • instanceCache
  • instancheInjector

providerCache初始化只有一個對象 providerCache = { $provide:{}} ,緊接着調用createInternalInjector 方法返回一個若干重兩級api(annotate,get等)賦值providerCache.$injector以及provoderInjector.則結果就變成這樣了:

providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      $injector:{
          get:getService,
          annotate:annotate,
          instantiate:instantiate,
          invoke:invoke,
          has:has
      }
}

providerInjector變成了這樣:

providerInjector{
      nvoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: has
}

同樣,instanceCacheinstanceInjector變成:

instanceCache:{
      $injector:{
          invoke: invoke,
          instantiate: instantiate,
          get: getService,
          annotate: annotate,
          has: has
      }
}


instanceInjector = {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: has
}

兩個重要方法:

  • loadModules
  • createInternalInjector
function loadModules(modulesToLoad){
    //剛纔說了,modulesToLoad長這樣:['ng',['$provider',function(){}], 'myApp']
    forEach(modulesToLoad, function(module) {
        if (isString(module)) {
             // module爲字符串時進入此判斷。
       moduleFn = angularModule(module);
            //迭代,把所有依賴模塊的runBlocks都取出
            runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
            //前面已經提到,每個module下有個_invokeQueue存了一堆controller,service之類東西,現在就可以遍歷拿出來運行啦
            for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
                //還記得我剛纔說了具體怎麼調用的把:第一個參數調用名爲第二個參數的方法,傳入第三個參數
                var invokeArgs = invokeQueue[i],
                    provider = providerInjector.get(invokeArgs[0]);
                provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
            }
        }else if(isFunction(module)){
            //這個我還沒找到…………
        }else if(isArray(module)){
             //這裏就是第二參數的情形了,用invoke方法執行後將結果存到runBlocks
            runBlocks.push(providerInjector.invoke(module));
        }
    }
    return runBlocks;
}

重量級函數createInternalInjector 閃亮登場,正是因爲有這個函數,才能實現那麼優雅的DI。

function createInternalInjector (){

    function getService(serviceName) {};

    function invoke(fn, self, locals){};

    function instantiate(Type, locals) {};

    return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: function(name) {
        //
      }
    };
}

這麼重要的函數實際上是一個工廠,最後返回五個方法。下面一一分析:

*annotate,只獲取依賴注入列表

function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        fnText = fn.toString().replace(STRIP_COMMENTS, '');
        argDecl = fnText.match(FN_ARGS);
        forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
          arg.replace(FN_ARG, function(all, underscore, name){
            $inject.push(name);
          });
        });
      }
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn');
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}

傳參有兩種形式,1.annotate(fn(injectName)) 以及 2.annotate([injectName,function(){}]).分別進入代碼中的兩個判斷,如果是隻傳了fn,且fn有參數,則利用fn.toString返回函數體本身,再通過正則取出injectName數組添加到fn.$inject上。 如果通過第二方式調用,取出所有$inject數組

  • invoke,通過annoate取出依賴注入,將依賴注入爲參數調用函數體。如:xx.invoke(function($window){});

    function invoke(fn, self, locals){
          var args = [],
              $inject = annotate(fn),
              length, i,
              key;
    
          for(i = 0, length = $inject.length; i < length; i++) {
            key = $inject[i];
            if (typeof key !== 'string') {
              throw $injectorMinErr('itkn',
                      'Incorrect injection token! Expected service name as string, got {0}', key);
            }
            args.push(
              locals && locals.hasOwnProperty(key)
              ? locals[key]
              : getService(key)
            );
        //省略若干
        //switch做了優化處理,羅列了一些常見的參數個數
    
       switch (self ? -1 : args.length) {
            case  0: return fn();
            //省略若干
    }
    
  • instantiate 此函數比較特殊,也比較有用,但一般不直接用,隱藏較深。

我們考慮這樣一種情況,一般較創建的寫法。

app.provider('myProvider',function(){ 
    //do something  
    this.$get = function(){
        return obj;
    }
});

假如我們想要在angular中用一些設計模式,我們換一種寫法:

app.provider('myProvider',myProvider);

    function myProvider(){
       BaseClass.apply(this,arguments);
    }
    unit.inherits(BaseClass,myProvider);
    extend(myProvider,{
       $get:function(){
            return something
       }
    });

這樣也可以實現我們需要的,而且可擴展。但如果我們沒有了解過instantiate這個方法,你看到此寫法會覺得疑惑不解。

instantiate(Type,locals)方法創建了一個空的構造函數,並繼承自傳入的Type,然後實例化得到instance,最後在調用invoke函數。

萬事俱備,只欠東風

做了這麼多準備,各種provider也有了,我們需要在頁面上展示效果了

injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
   function(scope, element, compile, injector, animate) {
    scope.$apply(function() {
      element.data('$injector', injector);
      compile(element)(scope);
    });
  }]
);

通過$apply將作用域轉入angular作用域,所謂angular作用域是指:angular採用dirity-check方式進行檢測,達到雙向綁定。

再利用compile函數編譯整個頁面文檔,識別出directive,按照優先級排序,執行他們的compilie函數,最後返回link function的結合。通過scope與模板連接起來,形成一個即時,雙向綁定。這個過程後續再分析。

到此,執行流程也就都出來了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章