[AngularJS面面观] 22. 依赖注入 --- 配置队列以及运行队列
在上一篇文章中,介绍了 本文会介绍定义与 配置队列(Config Queue)首先,我们来思考思考配置队列这一机制的必要性。现在,我们已经知道依赖注入实际上是由两个注入器协力完成的。我们经常使用的是实例注入器,但是当实例注入器找不到某个对象的时候,还是会去provider注入器那里需求帮助。所以provider注入器目前就是一个被动的角色,平常无人问津,有问题了就会有实例注入器来找它帮忙。如果我们需要直接使用provider注入器中管理的对象,也不是没有办法,可以通过创建一个 下面我们就来看看相关代码: var configBlocks = [];
var config = invokeLater('$injector','invoke','push',configBlocks);
moduleInstance.config = config;
// invokeLater函数的定义
function invokeLater(provider,method,insertMethod,queue) {
if (!queue) queue = invokeQueue;
// 还是利用柯里化将多个参数的函数转换为少数参数的函数
return function() {
// arguments才是我们在声明constant时实际传入的参数
queue[insertMethod || 'push']([provider,arguments]);
return moduleInstance;
};
}
以上就是配置队列在 var config = function() {
configBlocks['push']('$injector',arguments);
};
那么 if (isString(module)) {
// 和运行队列相关的代码,马上就会介绍
moduleFn = angularModule(module);
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
// 执行任务队列 - 这个我们已经相当熟悉了
runInvokeQueue(moduleFn._invokeQueue);
// 执行配置队列 - 这个是我们当前重点分析对象
runInvokeQueue(moduleFn._configBlocks);
}
有几个细节需要把握,在执行配置队列中定义的配置任务之前,会执行任务队列。它的目的是保证在进行任何配置之前,将定义的各种服务都注册好。所谓注册,也就是将各种服务的底层 // 如果你用Angular UI Router比较多,那么下面的$routeProvider就是$urlRouterProvider
module.config(function('$routeProvider') {
// ......
});
注意在 那么具体而言,这个行为是如何实现的呢。其实就是把对于 // 对照config的定义:(['$injector','invoke',arguments])
// invokeArgs[0]: '$injector'
// invokeArgs[1]: 'invoke':
// invokeArgs[2]: 类数组对象arguments
function runInvokeQueue(queue) {
var i,ii;
for (i = 0,ii = queue.length; i < ii; i++) {
var invokeArgs = queue[i],provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider,invokeArgs[2]);
}
}
注意看for循环中的这一行代码: // 相当于调用的providerInjector.get('$injector')
provider = providerInjector.get(invokeArgs[0]);
所以是期望从provider注入器中拿到 providerInjector = (providerCache.$injector =
createInternalInjector(providerCache,function(serviceName,caller) {
if (angular.isString(caller)) {
path.push(caller);
}
throw $injectorMinErr('unpr',"Unknown provider: {0}",path.join(' <- '));
})),
创建provider注入器之后将它保存在了自身的缓存中。因此对于 除了通过调用 // 第三个参数configFn就是需要执行的配置函数
function module(name,requires,configFn) {}
// 在内部同样还是通过调用config方法完成配置函数的注册
if (configFn) {
config(configFn);
}
使用哪一种方式进行配置函数的注册完全是看开发人员的个人爱好。但是我觉得还是使用 运行队列(Run Queue)在 /** * @ngdoc method * @name angular.Module#run * @module ng * @param {Function} 在注入器被创建后执行。用于应用的初始化。 * @description * 使用此方法来注册那些注入器加载完成所有模块后需要执行的任务。 */
run: function(block) {
runBlocks.push(block);
return this;
}
好像和 // createInjector函数中和运行队列相关的代码
// loadModules加载模块的函数返回值就是运行队列
var runBlocks = loadModules(modulesToLoad);
instanceInjector = protoInstanceInjector.get('$injector');
// 依次运行每个运行任务
forEach(runBlocks,function(fn) { if (fn) instanceInjector.invoke(fn); });
这里有三个值得注意的细节: function loadModules(modulesToLoad) {
var runBlocks = [];
forEach(modulesToLoad,function(module) {
// ......
try {
if (isString(module)) {
moduleFn = angularModule(module);
// module的递归加载在这里发生,运行队列的收集也在这里进行
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
// 执行当前正在被加载模块的任务队列以及配置队列
runInvokeQueue(moduleFn._invokeQueue);
runInvokeQueue(moduleFn._configBlocks);
} else if (isFunction(module)) {
// 如果模块是一个函数,那么使用provider注入器调用之,并将其返回值放入到执行队列
runBlocks.push(providerInjector.invoke(module));
} else if (isArray(module)) {
// 如果模块是一个数组对象,那么使用provider注入器调用之,并将其返回值放入到执行队列
runBlocks.push(providerInjector.invoke(module));
} else {
assertArgFn(module,'module');
}
} catch (e) {
// ......
}
});
return runBlocks;
}
可见,执行队列的收集工作是伴随着模块的递归加载而完成的。在加载一个模块的时候,只会对执行队列进行收集,而不像之前介绍的任务队列和配置队列那样,加载模块的同时就会调用 同时,我们也发现了 来分析比较一下下面两段代码便知: // 这是定义在module中的config方法,本质上它还是调用的provider注入器的invoke方法
var config = invokeLater('$injector',configBlocks);
// 这是在将module定义为函数时的调用方式,直白地使用了provider注入器的invoke方法
runBlocks.push(providerInjector.invoke(module));
它们两者的定义方式虽然不同,但是都有一颗 // 假设'a'是一个常量,'bProvider'是一个provider
angular.module(function(a,bProvider){});
这种方式提供了一个方便快捷地定义配置任务的方案,如果只是希望进行简单的配置工作,完全可以不通过传统的 // 假设'a'是一个常量,'bProvider'是一个provider,'cService'是一个service
angular.module(function(a,bProvider){
// 一些配置工作
return function(a,cService) {
// 一些初始化工作
};
});
和依赖注入相关的三种队列至此,我们已经接触到了和依赖注入密切相关的三种队列,下面简单总结回顾一下它们的区别:
调用 function constant(name,value) {
assertNotHasOwnProperty(name,'constant');
providerCache[name] = value;
instanceCache[name] = value;
}
除此之外,还有对应于
本文介绍的配置队列是为了提供一个配置各种providers的地方,通过provider注入器完成调用。任务队列和执行队列中定义的任务都是通过实例注入器进行调用的,因此它们无法直接地注入定义在provider注入器中的各种providers。配置队列的执行时机和任务队列相似,都是在加载某个模块的时候就会被执行,但是在顺序上它的执行发生在同模块的任务队列执行之后。
它用于定义一些模块的初始化工作。和任务队列一样,定义在运行队列中的任务都是通过实例注入器完成实际的调用工作的。但是和它以及配置队列不一样的是,它的执行时机实在所有模块全部加载完毕之后,此时所有的服务都已经完成注册工作(各路providers都已经准备好,知道如何初始化托管对象)。所以当运行队列中的任务被执行时,它是可以将需要的各种依赖都声明在其参数列表中的,哪怕这些依赖被定义在不同的模块中。 至此,我们已经了解到了和依赖注入实现息息相关的三种队列结构以及它们各自的特点和用法。在下一篇文章中会继续介绍 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |