angular 依赖注入

angular的依赖注入模式通过自动提前查找依赖以及为依赖提供目标,以此将依赖资源注入到需要它们的地方。

依赖注入服务可以使Web应用良好构建(比如分离表现层、 数据和控制三者的部件),并且松耦合(一个部件自己不需要解决部件之间的依赖问题,它们都被DI子系统所处理)。

使用场景

可以看到angular中的依赖注入服务使用非常频繁,注入的服务在下文称为provider。 如:

var app = angular.module('myApp', []);
app.factory('greeter', function() {
    return function(msg) {
        alert(msg) 
    }
});
app.value('testValue', '123');
//内置provider:$scope, 自定义的provider: greeter, testValue
app.controller('MyController', function($scope, greeter, testValue) {
    greeter('hello');
});

注入器(injector)

注入器用来解析、创建依赖服务,并通过invoke方法将依赖注入到需要的函数对象中。

解析provider标记

angular是如何根据参数的名称来找到相应的provider, injector对象中的annotate函数实现了这一功能

angular.injector().annotate(function($ABC, $myScope) {});
//output: ["$ABC", "$myScope"]
 
//采用正则去掉comment和空白字符,然后通过匹配functionBody种的function参数,获取需要的字符
var annotate = function() {
    var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
    var FN_ARG_SPLIT = /,/;
    var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
    var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
    $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);
                });
        });
    }
    return $inject;
};

invoke注入

这一步根据解析到的$inject数组,依次拿到对应provider.$get方法执行的结果,push到args数组。 同时让args作为被注入fn的参数调用fn。达到服务提供者和使用者的分离。

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];
         args.push(getService(key));
     }
     if (isArray(fn)) {
         fn = fn[length];
     }
     return fn.apply(self, args);
 }

invoke中调用的getService函数保证了所有的Provider为单例

function getService(serviceName) {
      if (cache.hasOwnProperty(serviceName)) {
           return cache[serviceName];
      } else {
          var provider = providerCache[name + providerSuffix];
          invoke(provider.$get, provider)
          return cache[serviceName] = invoke(provider.$get, provider);
      }          
} 

Provider

在angular的应用中,依赖注入的设计使得使用provider的成本大大降低。同时也是衔接MVC架构的基础, 你可以根据系统设计灵活分层, provider会让app中的service、model、controller很简单的联系起来。

Provider分为两种,一种是内置的,另外是为app module自定制的。

内置Provider

用一个简单的provider为例来分析:

function $LocationProvider() {
  this.hashPrefix = function(prefix) {  
  };
  this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
      function($rootScope, $browser, $sniffer, $rootElement) {
        var $location;
        $location.$$replace = false;
        return $location;
    }];
}

初始化的时候,如果是函数或者数组就通过新建一个constructor对象,使得该对象的prototype指向原来provider的prototype,同时拷贝构造函数中的属性。

function instantiate(provider) {
      var Constructor = function() {},
          instance, returnedValue;

      Constructor.prototype = provider.prototype;
      instance = new Constructor();
      returnedValue = ((typeof provider === 'array') ? provider[length-1] : provider).apply(Constructor)
      return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}

最后将生成的construcor对象放到providerCache中, 完成内置provider的初始化, 完整的代码如下:

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);
    }
//如果非函数或者对象,直接将provider放到cache中 return providerCache[name + providerSuffix] = provider_; }

以下是provider内置的一些provider,由于篇幅有限,列举了一些, 可以在angular module中通过名称注入到需要的地方。 更多请参考angular源码。

var angularModule = setupModuleLoader(window);
angularModule('ng', ['ngLocale'], ['$provide',
    function ngModule($provide) {
        $provide.provider({
            $$sanitizeUri: $$SanitizeUriProvider
        });
        $provide.provider('$compile', $CompileProvider).
        directive({
            ngSwitch: ngSwitchDirective,
            ngSwitchWhen: ngSwitchWhenDirective,
            ngSwitchDefault: ngSwitchDefaultDirective,
            ngOptions: ngOptionsDirective,
            ngTransclude: ngTranscludeDirective,
            ngModel: ngModelDirective,
            ngList: ngListDirective,
            ngChange: ngChangeDirective,
            required: requiredDirective,
            ngRequired: requiredDirective,
            ngValue: ngValueDirective
            ...
        }).
        directive({
            ngInclude: ngIncludeFillContentDirective
        }).
        directive(ngAttributeAliasDirectives).
        directive(ngEventDirectives);
        $provide.provider({
            $controller: $ControllerProvider,
            $document: $DocumentProvider,
            $filter: $FilterProvider,
            $interval: $IntervalProvider,
            $http: $HttpProvider,
            $location: $LocationProvider,
            $log: $LogProvider,
            $parse: $ParseProvider,
            $rootScope: $RootScopeProvider,
            $window: $WindowProvider,
            ...
        });
    }
]);

自定制Provider

angular可以通过以下6种方式实现自定制Provider注入:

  1. decorator
  2. constant
  3. value
  4. service
  5. factory
  6. provider

如:

<script type="text/javascript"> 
    var app = angular.module('myApp', []); 
    app.value('test', 'hello world'); 
    app.constant('test1', "hello world test"); 
     
    app.provider(function ($provide) { 
        $provide.decorator('test', function ($delegate) { 
            return $delegate + 'again'; 
        }); 
    });
    app.factory('myService', function () {  
        console.log('my service init');
    }); 
    app.service('myService1', function() { 
        console.log('my service1 init'); 
    });  
    app.controller('my controller', function($scope, test, test1, myService, myService1) { 
       ...
    } 
</script>

分析一下代码:

//将provider直接保存到providerCache中
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_;
}
//由于所有的provider默认都必须实现$get方法,所以采用工厂方法对factoryFn进行封装,再保存到providerCache中
function factory(name, factoryFn) {
    return provider(name, {
        $get: factoryFn
    });
}
//调用工厂方法, 处理当constructor返回非对象的时候, 新建对象返回,并复用constructor的prototype,拷贝constructor的实例属性
function service(name, constructor) {
    return factory(name, ['$injector', function($injector) {
            return $injector.instantiate(constructor);
        }
    ]);
}
//调用工厂方法
function value(name, val) {
    return factory(name, function(){ return val; });
}
//不可修改的静态属性,也可以直接作为provider提供
function constant(name, value) {
    providerCache[name] = value;
    instanceCache[name] = value;
}
//改写provider的$get, 并将原来provider的返回值作为参数调用装饰函数。
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
            });
        }; 
}

参考

  • https://github.com/angular/angular.js