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注入:
- decorator
- constant
- value
- service
- factory
- 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