Angular.JS学习之依赖注入$injector详析
前言
在依赖注入(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部分,通过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
对象上,而我们通过angular.module(′app′,[]).provider(...)
方式调用的provider函数,会在module加载期间将调用(该调用抽象成一个数组,即[provider,method,arguments])绑定在内部的invokequeue数组中,最终在providercache.provide
对象上调用provider方法,其他的controller,directive等方法类似,不过是绑定在providercache.controllerprovider,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方法后面添加一个拦截函数,并通过传递依赖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函数,获取[“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有没有进一步的了解呢?以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。
上一篇: 谷歌正从搜索优先转变为AI优先
下一篇: 使用Angular.js开发的注意事项