angular源碼分析:injector.js文件分析——angular中的依賴注入式如何實現的(續)


昨天晚上寫完angular源碼分析:angular中jqLite的實現——你可以丟掉jQuery了,給今天定了一個題angular源碼分析:injector.js文件,以及angular的加載流程,但是想了想,加載流程還是放到后面統一再講比較好。
如果你沒有看過筆者的angular源碼分析:angular中的依賴注入式如何實現的,可以點擊看看,在其中講過的內容,我將不會再這里重復,這一期將作那一期的補充。

一、從createInjector函數開始

先省去具體實現,總體看看:函數擁有兩個參數,modulesToLoad, strictDi,從單詞命名上來看,第一個參數是要沒加載的模塊,第二參數是嚴格的依賴注入;另外函數對象本身綁定了一個annotate,在之前我們講過annotate是一個可以將函數中的參數提出來的函數。

function createInjector(modulesToLoad, strictDi) { ...}
createInjector.$$annotate = annotate;

二、理解createInjector函數的實現

先上代碼:

  strictDi = (strictDi === true);
  var INSTANTIATING = {},
      providerSuffix = 'Provider',
      path = [],
      loadedModules = new HashMap([], true),
      providerCache = {  //用存放provider的cache
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      providerInjector = (providerCache.$injector =
          createInternalInjector(providerCache, function(serviceName, caller) { //調用createInternalInjector生成內部的注入器
            if (angular.isString(caller)) {
              path.push(caller);
            }
            throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
          })),
      instanceCache = {},  //用來存放服務實例的cache
      instanceInjector = (instanceCache.$injector =
          createInternalInjector(instanceCache, function(serviceName, caller) {   
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
          }));


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

  return instanceInjector;

1.這個函數做了什么。

基本上是定義一堆東西,其中最重要的是providerCache和providerInjector 以及instanceCache和instanceInjector。關於createInternalInjector這個函數,在angular源碼分析:angular中的依賴注入式如何實現的中講過,主要功能是利用提供的cache(第一個參數u)和factory(第二參數),構造一個內部注入器,其本身也是工廠函數。

2.providerCache 和$provide

我們可以這樣理解,providerCache中存放的就是各種服務的提供者的實例。比如定義一個服務,叫"dapeng",那么提供者就是"dapengProvider"。而這個容器(providerCache)在初始化時,就默認放入了一個$provide。

      providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },

很眼熟吧,這是不是可以用於定義服務的幾種方式呢?

3.forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });中的loadModules函數:

  ////////////////////////////////////
  // Module Loading
  ////////////////////////////////////
  function loadModules(modulesToLoad) {
    assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
    var runBlocks = [], moduleFn;
    forEach(modulesToLoad, function(module) {
      if (loadedModules.get(module)) return;
      loadedModules.put(module, true);

      function runInvokeQueue(queue) {  //執行調用隊列
        var i, ii;
        for (i = 0, ii = queue.length; i < ii; i++) {
          var invokeArgs = queue[i],
              provider = providerInjector.get(invokeArgs[0]);

          provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
        }
      }

      try {
        if (isString(module)) {
          moduleFn = angularModule(module); //加載模塊
          runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); //遞歸加載依賴模塊,獲取所有模塊的run函數定義的代碼。
          runInvokeQueue(moduleFn._invokeQueue);  //moduleFn._invokeQueue是什么鬼,先留坑在此
          runInvokeQueue(moduleFn._configBlocks); //moduleFn._configBlocks是什么鬼,也留坑在此
        } else if (isFunction(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else if (isArray(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else {
          assertArgFn(module, 'module');
        }
      } catch (e) {
        if (isArray(module)) {
          module = module[module.length - 1];
        }
        if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
          // Safari & FF's stack traces don't contain error.message content
          // unlike those of Chrome and IE
          // So if stack doesn't contain message, we create a new string that contains both.
          // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
          /* jshint -W022 */
          e = e.message + '\n' + e.stack;
        }
        throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
                  module, e.stack || e.message || e);
      }
    });
    return runBlocks;
  }

angularModule是什么鬼,留坑先不講。可以先理解為,用於加載一個module。
runBlocks將得到一個數組,這個數組的元素是一些函數,這些函數是定義模塊后通過run(function(){})注冊的函數。
runInvokeQueue函數,將執行調用隊列,可以從這個函數的實現上來看,參數queue應該是一個二維數組。[['name','index',params]],這個函數將循環處理queue數組。
** moduleFn._invokeQueue 和 moduleFn._configBlocks** 本期先不講,留坑在此,等講“加載流程”再講。
那么,這個函數最紅就是返回的一個函數數組:runBlocks。
可以基本推出:forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });是執行所有的模塊中run的代碼,而在run的代碼執行前,先執行了服務的定義代碼和模塊config代碼。

三、$provider

還是先上代碼:

  ////////////////////////////////////
  // $provider
  ////////////////////////////////////

  function supportObject(delegate) {
    return function(key, value) {
      if (isObject(key)) { //如果key是一個對象
        forEach(key, reverseParams(delegate));
      } else {
        return delegate(key, value);
      }
    };
  }

  function provider(name, provider_) {
    assertNotHasOwnProperty(name, 'service');
    if (isFunction(provider_) || isArray(provider_)) {
      provider_ = providerInjector.instantiate(provider_);
    }
    if (!provider_.$get) {
      throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
    }
    return providerCache[name + providerSuffix] = provider_;
  }

  function enforceReturnValue(name, factory) {
    return function enforcedReturnValue() {
      var result = instanceInjector.invoke(factory, this);
      if (isUndefined(result)) {
        throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name);
      }
      return result;
    };
  }

  function factory(name, factoryFn, enforce) {
    return provider(name, {
      $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
    });
  }

  function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
      return $injector.instantiate(constructor);
    }]);
  }

  function value(name, val) { return factory(name, valueFn(val), false); }

  function constant(name, value) {
    assertNotHasOwnProperty(name, 'constant');
    providerCache[name] = value;
    instanceCache[name] = value;
  }

  function decorator(serviceName, decorFn) {
    var origProvider = providerInjector.get(serviceName + providerSuffix),
        orig$get = origProvider.$get;

    origProvider.$get = function() {
      var origInstance = instanceInjector.invoke(orig$get, origProvider);
      return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
    };
  }

1.supportObject函數

這里先引用一下reverseParams的實現

   function reverseParams(iteratorFn) {
       return function(value, key) { iteratorFn(key, value); };
   }

來看看下面的代碼將會得到什么,就知道這個函數在做什么了:

  function agent(key,vlue){
    console.log(key + '--->'+ value);
  }

  var new_agent = supportObject(agent);
  new_agent({a:123,b:456,c:'abc'});
  new_agent('key','value');

2.provider函數,參數name,provider_

作用,創建服務的提供者,serviceProvider,並且用providerCache保存起來。

3.enforceReturnValue

直接調用instanceInjector.invoke來生成服務。

4.factory

調用provide函數,由函數自身提供一個Provider。

5.service,繼續簡化服務的定義。

這意味者,可以給service的第二參數傳遞一個構成函數,service會利用構造函數“new”出一個服務對象來。如果你已經有一個構造函數,需要定義這個構造函數生成的對象為服務,可以考慮使用這個方法。

6.value,繼續簡化服務的定義。

第二個參數,是一個對象(可以是一個基礎類型的值)或者是一個可以返回對象的函數。如果你定義的服務是一個對象,可以考慮用這個方法。

7.constant

可以看到providerCache和instanceCache中的存儲用的鍵是一個,就是說,通過這個函數定義的服務,可以在模塊的config階段和run階段同時有效。

8.decorator,裝飾模式的實現

通過代碼分析,可以得到結論:decorator可以對一個已有的服務進行重新裝飾。
舉例

decorator('exist_service',function($delegate){
   $delegate.add_method = function(){
      console.log('this method is added');
   };
});

上面的代碼,將會向服務“exist_service”增加一個add_method方法。
在最新的angular版本中,已經可以采用angular.module('xxx').decorator('exist_service',function($delegate){})的方式來使用decorator,我們講的這個版本,還只能在config中使用。

上一期:angular源碼分析:angular中jqLite的實現——你可以丟掉jQuery了
下一期:angular源碼分析:angular的整個加載流程


免責聲明!

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



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