AngularJS源碼分析之依賴注入$injector


開篇

            隨着javaEE的spring框架的興起,依賴注入(IoC)的概念徹底深入人心,它徹底改變了我們的編碼模式和思維。在IoC之前,我們在程序中需要創建一個對象很簡單也很直接,就是在代碼中new Object即可,有我們自己負責創建、維護、修改和刪除,也就是說,我們控制了對象的整個生命周期,直到對象沒有被引用,被回收。誠然,當創建或者維護的對象數量較少時,這種做法無可厚非,但是當一個大項目中需要創建大數量級的對象時,僅僅依靠程序員來進行維護所有對象,這是難以做到的,特別是如果想在程序的整個生命周期內復用一些對象,我們需要自己寫一個緩存模塊對所有對象進行緩存,這加大了復雜度。當然,IoC的好處並不僅限於此,它也降低了對依賴的耦合度,不必在代碼中進行引用或者傳參即可操作依賴。

         在js中,我們可以這樣引入依賴

  1. 使用全局變量引用
  2. 在需要的地方通過函數參數傳遞

        使用全局變量的壞處自不必說,污染了全局的名字空間,而通過函參傳遞引用,也可以通過兩種方法實現:

  1. 閉包傳遞
  2. 后台解析出依賴對象,並通過Function.prototype.call進行傳參

而在AngularJS中,依賴注入是通過后者實現的,接下來的幾節將會介紹IoC模塊的具體實現。

獲取依賴

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
    // 獲取服務名
var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var $injectorMinErr = minErr('$injector');

function anonFn(fn) {
  // For anonymous functions, showing at the very least the function signature can help in
  // debugging.
  var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
      args = fnText.match(FN_ARGS);
  if (args) {
    return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')';
  }
  return 'fn';
}

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

  if (typeof fn === 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      if (fn.length) {
        if (strictDi) {
          if (!isString(name) || !name) {
            name = fn.name || anonFn(fn);
          }
          throw $injectorMinErr('strictdi',
            '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
        }
        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;
}

annotate函數通過對入參進行針對性分析,若傳遞的是一個函數,則依賴模塊作為入參傳遞,此時可通過序列化函數進行正則匹配,獲取依賴模塊的名稱並存入$inject數組中返回,另外,通過函數入參傳遞依賴的方式在嚴格模式下執行會拋出異常;第二種依賴傳遞則是通過數組的方式,數組的最后一個元素是需要使用依賴的函數。annotate函數最終返回解析的依賴名稱。

注入器的創建

          AngularJS的API也提供了$injector部分,通過$injector可以使用get,has,instantiate,invoke以及上節提到的annotate等方法,通過源碼可以更清晰的理解。

function createInternalInjector(cache, factory) {
    // 對服務注入器 providerInjector而言,只根據服務名獲取服務,factory會拋出異常
    function getService(serviceName, caller) {
      if (cache.hasOwnProperty(serviceName)) {
        if (cache[serviceName] === INSTANTIATING) {
          throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
                    serviceName + ' <- ' + path.join(' <- '));
        }
        return cache[serviceName];
      } else {
        try {
          path.unshift(serviceName);
          cache[serviceName] = INSTANTIATING;
          return cache[serviceName] = factory(serviceName, caller);
        } catch (err) {
          if (cache[serviceName] === INSTANTIATING) {
            delete cache[serviceName];
          }
          throw err;
        } finally {
          path.shift();
        }
      }
    }

    function invoke(fn, self, locals, serviceName) {
      if (typeof locals === 'string') {
        serviceName = locals;
        locals = null;
      }

      var args = [],
          // 解析並獲取注入服務列表
          $inject = annotate(fn, strictDi, serviceName),
          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, serviceName)
        );
      }
      if (isArray(fn)) {
        fn = fn[length];
      }

      // http://jsperf.com/angularjs-invoke-apply-vs-switch
      // #5388
      return fn.apply(self, args);
    }

    function instantiate(Type, locals, serviceName) {
      // Check if Type is annotated and use just the given function at n-1 as parameter
      // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
      // Object creation: http://jsperf.com/create-constructor/2
      var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
      var returnedValue = invoke(Type, instance, locals, serviceName);

      return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
    }

    return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: annotate,
      has: function(name) {
        return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
      }
    };
  }

createInternalInjector方法創建$injector對象,傳遞的參數分別為緩存對象和工廠函數。在具體實現中,AngularJS創建了兩個injector對象--providerInjector和instanceInjector(這兩個對象的不同主要是createInternalInjector方法傳遞的緩存對象不同),而通過angular.injector()導出的就是instanceInjector。對於providerInjector,主要用來獲取服務的提供者,即serviceProvider。而對於instanceInjector而言,主要用於執行從providerInjector獲取的provider對象的$get方法,生產服務對象(依賴),並將服務對象傳遞給相應的函數,完成IoC。

首先從get方法說起,get方法主要獲取指定名稱的服務,通過angular的injector方法獲取的是instanceInjector,而當緩存中沒有該服務對象(依賴)時,我們需要執行factory(serviceName, caller)方法,我們看看對於的factory函數:

instanceInjector = (instanceCache.$injector =
          createInternalInjector(instanceCache, function
(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return
 instanceInjector.invoke(provider.$get, provider, undefined, serviceName); }));

紅色部分即為factory函數,它顯示通過providerInjector獲取相應服務的提供者serviceProvider,然后調用instanceInjector的invoke方法在serviceProvider上下文執行serviceProvider的$get方法,返回服務對象並保存在緩存中。這樣,便完成了服務對象(依賴)的獲取和緩存。

invoke方法也很簡單,它的入參分別問fn, self, locals, serviceName,即要執行的函數,函數執行的上下文,提供的options選項和服務名。首先獲取函數的所有依賴名,通過annotate方法完成之后,如果options中提供了對於名稱的依賴,則使用,否則通過get方法獲取依賴,最后傳入函數,並將函數的執行結果返回。invoke返回的結果往往是一個服務對象。

instantiate方法主要根據提供的構造函數創建一個示例,用作依賴或提供服務。值得一提的是並沒有通過new關鍵字創建對象,而是通過ECMA5提供的Object.create來繼承函數的原型對象實現,非常巧妙。

has方法則是相繼判斷serviceProvider和service是否存在於緩存中。

至此,$injector對象創建完畢。

注冊服務(依賴)

          服務不可能憑空而來,我們需要自己實現或者外部引入服務或依賴。所以,注冊服務的模塊也是值得深究的。AngularJS提供了多種注冊服務的API,但是我們着重關注的是provider方法,其他factory,service方法都是基於此進行構建的。

          這些方法(provider,factory等)綁定在providerCache.$provide對象上,而我們通過angular.module('app',[]).provider(...)方式調用的provider函數,會在module加載期間將調用(該調用抽象成一個數組,即[provider,method,arguments])綁定在內部的_invokeQueue數組中,最終在providerCache.$provide對象上調用provider方法,其他的controller,directive等方法類似,不過是綁定在providerCache.$controllerProvider,providerCache.$compileProvider對象上。

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

provider方法需要兩個參數,一個是服務名(依賴名),另外是工廠方法或者是一個包含依賴和工廠方法的數組。首先通過providerInjector創建工廠方法的一個實例,並添加到providerCache中,返回。

factory方法只是將第二個參數封裝成了一個包含$get方法的對象,即serviceProvider,緩存。並不復雜。

而service方法則嵌套注入了$injector服務,即instanceInjector,它會創建構造函數的實例,作為服務對象。

value方法僅僅封裝了一個provider,其$get方法返回value值。

constant方法則將value的值分別存入providerCache和instanceCache中,並不需要invoke獲取其value值。

而比較特殊且擴展性較高的decorator方法,是在serviceProvider的$get方法后面添加一個攔截函數,並通過傳遞依賴$delegate來獲取原先invoke $get方法返回的服務對象。我們可以通過decorator來對服務進行擴展,刪除等操作。

流程

            最后,在基本的實現已經完成的基礎上,我們走一遍具體的注入流程,更易於我們的深入理解。

angular.module("app",[])
     .provider("locationService",function(){
        ...
    })
    .controller("WeatherController",function($scope,locationService,$location){
        locationService.getWeather()
            .then(function(data){
                $scope.weather = data;
            },function(e){
                console.log("error message: "+ e.message)
            });
    })

我們不關心具體的代碼實現,僅僅使用上述代碼作為演示。

首先確定AngularJS上下文的范圍,並且獲取依賴模塊(在此處為空);

繼續注冊服務(依賴),將serviceProvider緩存至providerCache中;

聲明控制器;

在此獲取$injector示例,通過執行invoke函數,獲取[“$scope”,”locationService”,”$location”]依賴列表,通過$injector的get方法獲取相應的依賴對象。對於$scope和$location服務而言,在AngularJS初始化時已經注入到Angular中,因此可以獲取相應的provider對象,執行相關的方法返回$scope和$location對象,而locationService則在provider中進行了聲明,因此獲取到locationServiceProvider對象,通過調用instanceInjector.invoke(locationServiceProvider.$get, locationServiceProvider, undefined, “locationService”)返回locationService對象。

最后將所有的依賴組裝成數組[$scope,locationService,$location]作為參數傳遞給匿名函數執行。

至此,依賴注入完成。


免責聲明!

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



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