Angular.JS学习之依赖注入$injector详析
前言
在依赖注入(IoC)之前,我们在程序中需要创建一个对象很简单也很直接,就是在代码中newObject即可,有我们自己负责创建、维护、修改和删除,也就是说,我们控制了对象的整个生命周期,直到对象没有被引用,被回收。诚然,当创建或者维护的对象数量较少时,这种做法无可厚非,但是当一个大项目中需要创建大数量级的对象时,仅仅依靠程序员来进行维护所有对象,这是难以做到的,特别是如果想在程序的整个生命周期内复用一些对象,我们需要自己写一个缓存模块对所有对象进行缓存,这加大了复杂度。当然,IoC的好处并不仅限于此,它也降低了对依赖的耦合度,不必在代码中进行引用或者传参即可操作依赖。
在js中,我们可以这样引入依赖
1、使用全局变量引用
2、在需要的地方通过函数参数传递
使用全局变量的坏处自不必说,污染了全局的名字空间,而通过函参传递引用,也可以通过两种方法实现:
1、闭包传递
2、后台解析出依赖对象,并通过Function.prototype.call进行传参
而在AngularJS中,依赖注入是通过后者实现的,接下来的几节将会介绍IoC模块的具体实现。
获取依赖
varFN_ARGS=/^function\s*[^\(]*\(\s*([^\)]*)\)/m;
varFN_ARG_SPLIT=/,/;
//获取服务名
varFN_ARG=/^\s*(_?)(\S+?)\1\s*$/;
varSTRIP_COMMENTS=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var$injectorMinErr=minErr('$injector');
functionanonFn(fn){
//Foranonymousfunctions,showingattheveryleastthefunctionsignaturecanhelpin
//debugging.
varfnText=fn.toString().replace(STRIP_COMMENTS,''),
args=fnText.match(FN_ARGS);
if(args){
return'function('+(args[1]||'').replace(/[\s\r\n]+/,'')+')';
}
return'fn';
}
functionannotate(fn,strictDi,name){
var$inject,
fnText,
argDecl,
last;
if(typeoffn==='function'){
if(!($inject=fn.$inject)){
$inject=[];
if(fn.length){
if(strictDi){
if(!isString(name)||!name){
name=fn.name||anonFn(fn);
}
throw$injectorMinErr('strictdi',
'{0}isnotusingexplicitannotationandcannotbeinvokedinstrictmode',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;
}
}elseif(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部分,通过injector可以使用get,has,instantiate,invoke以及上节提到的annotate等方法,通过源码可以更清晰的理解。
functioncreateInternalInjector(cache,factory){
//对服务注入器providerInjector而言,只根据服务名获取服务,factory会抛出异常
functiongetService(serviceName,caller){
if(cache.hasOwnProperty(serviceName)){
if(cache[serviceName]===INSTANTIATING){
throw$injectorMinErr('cdep','Circulardependencyfound:{0}',
serviceName+'<-'+path.join('<-'));
}
returncache[serviceName];
}else{
try{
path.unshift(serviceName);
cache[serviceName]=INSTANTIATING;
returncache[serviceName]=factory(serviceName,caller);
}catch(err){
if(cache[serviceName]===INSTANTIATING){
deletecache[serviceName];
}
throwerr;
}finally{
path.shift();
}
}
}
functioninvoke(fn,self,locals,serviceName){
if(typeoflocals==='string'){
serviceName=locals;
locals=null;
}
varargs=[],
//解析并获取注入服务列表
$inject=annotate(fn,strictDi,serviceName),
length,i,
key;
for(i=0,length=$inject.length;i<length;i++){
key=$inject[i];
if(typeofkey!=='string'){
throw$injectorMinErr('itkn',
'Incorrectinjectiontoken!Expectedservicenameasstring,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
returnfn.apply(self,args);
}
functioninstantiate(Type,locals,serviceName){
//CheckifTypeisannotatedandusejustthegivenfunctionatn-1asparameter
//e.g.someModule.factory('greeter',['$window',function(renamed$window){}]);
//Objectcreation:http://jsperf.com/create-constructor/2
varinstance=Object.create((isArray(Type)?Type[Type.length-1]:Type).prototype);
varreturnedValue=invoke(Type,instance,locals,serviceName);
returnisObject(returnedValue)||isFunction(returnedValue)?returnedValue:instance;
}
return{
invoke:invoke,
instantiate:instantiate,
get:getService,
annotate:annotate,
has:function(name){
returnproviderCache.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){varprovider=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对象上,而我们通过angular.module(′app′,[]).provider(...)方式调用的provider函数,会在module加载期间将调用(该调用抽象成一个数组,即[provider,method,arguments])绑定在内部的invokeQueue数组中,最终在providerCache.provide对象上调用provider方法,其他的controller,directive等方法类似,不过是绑定在providerCache.controllerProvider,providerCache.controllerProvider,providerCache.compileProvider对象上。
functionprovider(name,provider_){
assertNotHasOwnProperty(name,'service');
if(isFunction(provider_)||isArray(provider_)){
provider_=providerInjector.instantiate(provider_);
}
if(!provider_.$get){
throw$injectorMinErr('pget',"Provider'{0}'mustdefine$getfactorymethod.",name);
}
returnproviderCache[name+providerSuffix]=provider_;
}
functionenforceReturnValue(name,factory){
returnfunctionenforcedReturnValue(){
varresult=instanceInjector.invoke(factory,this);
if(isUndefined(result)){
throw$injectorMinErr('undef',"Provider'{0}'mustreturnavaluefrom$getfactorymethod.",name);
}
returnresult;
};
}
functionfactory(name,factoryFn,enforce){
returnprovider(name,{
$get:enforce!==false?enforceReturnValue(name,factoryFn):factoryFn
});
}
functionservice(name,constructor){
returnfactory(name,['$injector',function($injector){
return$injector.instantiate(constructor);
}]);
}
functionvalue(name,val){returnfactory(name,valueFn(val),false);}
functionconstant(name,value){
assertNotHasOwnProperty(name,'constant');
providerCache[name]=value;
instanceCache[name]=value;
}
//在服务实例化之后进行调用(拦截),拦截函数中注入实例化的服务,可以修改,扩展替换服务。
functiondecorator(serviceName,decorFn){
varorigProvider=providerInjector.get(serviceName+providerSuffix),
orig$get=origProvider.$get;
origProvider.$get=function(){
varorigInstance=instanceInjector.invoke(orig$get,origProvider);
returninstanceInjector.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方法后面添加一个拦截函数,并通过传递依赖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("errormessage:"+e.message)
});
})
我们不关心具体的代码实现,仅仅使用上述代码作为演示。
首先确定AngularJS上下文的范围,并且获取依赖模块(在此处为空);
继续注册服务(依赖),将serviceProvider缓存至providerCache中;
声明控制器;
在此获取injector示例,通过执行invoke函数,获取[“injector示例,通过执行invoke函数,获取[“scope”,”locationService”,”location”]依赖列表,通过location”]依赖列表,通过injector的get方法获取相应的依赖对象。对于scope和scope和location服务而言,在AngularJS初始化时已经注入到Angular中,因此可以获取相应的provider对象,执行相关的方法返回scope和scope和location对象,而locationService则在provider中进行了声明,因此获取到locationServiceProvider对象,通过调用instanceInjector.invoke(locationServiceProvider.$get,locationServiceProvider,undefined,“locationService”)返回locationService对象。
最后将所有的依赖组装成数组[scope,locationService,scope,locationService,location]作为参数传递给匿名函数执行。
总结
至此,依赖注入完成。大家对依赖注入$injector有没有进一步的了解呢?以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。