依赖注入
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
内置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