AngularJS Injector和Service的工作机制
要了解angularJS里的injector和Service是如何工作的,需要阅读/src/auto/injector.js。另外要结合/src/loader.js才能明白它的应用场景。
auto/injector.js
辅助函数
在javascript中,如果对一个函数调用toString方法,会打印出这个函数的source code。angularJS的inject也是基于这个功能实现的。因此,在injector.js文件的一开头定义了几个辅助函数:
- extractArgs(fn)
这个函数将从fn.toString()中提取出参数字段作为一个string返回。 - annotate(fn)
这是根据函数签名为函数添加注解(annotation)的方法。
injector的定义
angularJS中一共存在两个injector:providerInjector和instanceInjector。都是由createInternalInjector(cache, factory)函数创建的,要了解injector的组成必须先看明白createInternalInjector这个函数。
一个injector中有以下几个公开方法:
- invoke(fn, self, locals, serviceName)
- instantiate(Type, locals, serviceName)
- get(serviceName, caller)
- annotate
- has(name)
另外在createInternalInjector中定义了一个内部方法injectionArgs(fn, locals, serviceName), 该方法首先获取fn的函数签名中的参数列表,根据参数名从locals中查找参数值,当locals中不存在时,调用injector的get方法去查找参数值,最后列表返回所有参数值,就这样AngularJS根据函数签名获取了函数依赖。在invoke, instantiate中均会用这个方法来获取依赖然后注入到函数中。
接下来我们一一了解injector的几个公开方法:
get(serviceName, caller)
- 当前cache中有serviceName键,但是键值为INSTANTIATING,表示出现了循环依赖,丢出错误,否则返回键值。
- 而cache中不存在时,将键值置为INSTANTIATING,然后调用factory(service, caller)去根据service获取实例,将结果置为键值。这样如果在创建过程中再次遇到该依赖,就会发现循环依赖。另外创建成功后,该值也就留在了cache中。
invoke(fn, self, locals, serviceName)
- 首先调用injectionArgs(fn, locals, serviceName)获取函数依赖。
- 用获取的依赖来调用该函数,返回结果。
instantiate(Type, lcoals, serviceName)
与invoke类似,利用Type的函数签名获取依赖然后实例化Type
总结一下,injector就是围绕其创建时传入的cache和factory工作的,其get方法会首先从cache中取缓存,否则则通过factory方法去生成实例返回。其invoke方法会根据函数签名去获取参数依赖,然后调用函数。
AngularJS中的Injector
上一节讲了injector的基本结构,AngularJS中一共有两个injector,一个负责serviceProvider的注入,一个负责service实例的注入。阅读createInjector(modulesToLoad, strictDi)函数看到它们的初始化过程。
providerInjector
函数首先创建了providerCache,然后用这个cache和一个factory方法创建了providerInjector,但是该factory方法只会报出"unknown provider"错误。那如何往cache中添加serviceProvider呢?这里providerCache中预置了一个键值为"$provide"的对象,该对象提供一系列方法provider, factory, service, value, constant等供调用者向providerCache中添加serviceProvider.instanceInjector
然后函数创建的就是instanceProvider,这里的初始cache是一个空字典,一旦需要的实例不存在cache中时,factory函数就会首先使用providerInjector.get去获取该service的provider,然后再通过instanceProvider.invoke方法去调用serviceProvider.$get方法,这个过程中 又会继续实例化该service的各种依赖,最终获取到service实例,同时根据injector的工作机制,该实例自此被加入到instanceInjector的cache中,下次就可以直接获取了。
函数同时将一个只会返回instanceInjector的provider以"$injector"为键加入了providerCache中,所以instanceInjector可以通过"$injector"获取到它自身。loadModules
在两个injector都初始化完成后,函数调用loadModules(modulesToLoad),获得一个runBlocks列表,然后对其中每一个block调用instanceInjector.invoke(block),最后返回instanceInjector。很明显,这个过程中加载了modules中各种service的定义,要了解这个工作过程,我们需要看一下loadModules定义。- loadModules(modulesToLoad)
该函数首先把每个module以及每个module所require的module中的runBlocks都合并到一个列表中。
然后调用:
runInvokeQueue(moduleFn._invokeQueue);
runInvokeQueue(moduleFn._configBlocks);
至于runInvokeQueue,在后面遇到使用时再讲明白。总之loadModules函数将合并的runBlocks列表返回。
- loadModules(modulesToLoad)
loader.js
要明白各个service是怎么加载的,我们需要看一下angularJS对于Modules的定义,这里需要了解一下loader.js。
AngularJS中的Modules
loader.js中的module(name, requires, configFn)就是我们最常使用的angular.module("***", [], [])方法。它返回一个moduleInstance,其中自带了_invokeQueue, _configBlocks和_runBlocks三个队列,不过这三个队列一开始都是空的。
module函数中定义了两个重要的内部方法invokeLater和invokeLaterAndSetModuleName,了解了这两个方法才能明白上面三个queue是怎么用的。
invokeLater(provider, method, insertMethod, queue)
当未指定queue时,queue默认为module的invokeQueue.
当未指定insertMethod时,默认为"push"。
该方法返回一个方法f,当f被调用时,向queue中通过insertMethod加入一个[provider, method, f的arguments]组成的数列。queue最后就被前述injector.js中的runInvokeQueue调用了,现在我们就可以看看runInvokeQueue的定义了。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]);
}
}
对于queue中的参数arg,从providerInjector中get(arg[0]),然后调用arg[1]方法,参数是arg[3]。arg[0]是invokeLater的参数中的provider, arg[1]是method,arg[3]是返回函数f被调用时候的参数。
对于仍然糊涂的同学,我们看看moduleInstance中的value方法的定义:value : invokeLater("$provide", "value"),
因此angular.module("A").value(name, val),会在module A的invokeQueue中插入['$provide', 'value', [name, val]],这个queue会在加载module A的时候被传递给runInvokeQueue作为参数,执行providerInjector.get("$provide")['value'](name, val), 结合之前providerInjector的初始化过程,该函数执行结果为加载了一个返回val的名为name的service。
invokeLaterAndSetModuleName(provider, method)
这个方法的返回值也是一个方法,返回方法的签名是(name, factory),调用时会将factory.$$moduleName置为module的name,然后直接想invokeQueue中push进[provider, method, arguments]
我们可以再看看module.directive的定义:directive: invokeLaterAndSetModuleName('$compileProvider', 'directive');
因此当我们调用angular.module("A").directive("DA", function(){})时,最终向invokeQueue中插入了["$compileProvier", "directive", ["DA", function(){}]]参数。不用担心$compileProvider现在还没有,因为这只是插入invokeQueue,只有在模块被加载时才会执行,而$compileProvider属于ng模块,该模块会被默认加载。
于是我们在模块中定义的各种service都会被加载到queue中,在模块加载时去初始化service的provider,然后在injector做依赖注入时,从provider中生成service实例。
AngularJS Injector和Service的工作机制的更多相关文章
- android 6.0 高通平台sensor 工作机制及流程(原创)
最近工作上有碰到sensor的相关问题,正好分析下其流程作个笔记. 这个笔记分三个部分: sensor硬件和驱动的工作机制 sensor 上层app如何使用 从驱动到上层app这中间的流程是如何 Se ...
- ASP.NET Web Service如何工作(1)
ASP.NET Web Service如何工作(1) [日期:2003-06-26] 来源:CSDN 作者:sunnyzhao(翻译) [字体:大 中 小] Summary ASP.NET Web ...
- rsync工作机制(翻译)
本篇为rsync官方推荐文章How Rsync Works的翻译,主要内容是Rsync术语说明和简单版的rsync工作原理.本篇没有通篇都进行翻译,前言直接跳过了,但为了文章的完整性,前言部分的原文还 ...
- tomcat中Servlet的工作机制
在研究Servlet在tomcat中的工作机制前必须先看看Servlet规范的一些重要的相关规定,规范提供了一个Servlet接口,接口中包含的重要方法是init.service.destroy等方法 ...
- Android IPC机制—Binder的工作机制
进程和线程的关系 IPC机制即为跨进程通信,是inter-Process Communication的缩写.是指两个进程之间进行通信.在说进程通信之前,我们的弄明白什么是线程,什么是进程.进程和线程是 ...
- angularjs中factory, service和provider
在Angular里面,services作为单例对象在需要到的时候被创建,只有在应用生命周期结束的时候(关闭浏览器)才会被清除.而controllers在不需要的时候就会被销毁了(因为service的底 ...
- Binder的工作机制浅析
在Android开发中,Binder主要用于Service中,包括AIDL和Messenger,其中Messenger的底层实现就是AIDL,所以我们这里通过AIDL来分析一下Binder的工作机制. ...
- rsync(五)工作机制
当我们讨论rsync时,我们使用了一些特殊的术语来代表不同的进程以及它们在任务执行过程中所扮演的角色.人类为了更方便.更准确地交流,使用同一种语言是非常重要的:同样地,在特定的上下文环境中,使用固定的 ...
- 手写Spring框架,加深对Spring工作机制的理解!
在我们的日常工作中,经常会用到Spring.Spring Boot.Spring Cloud.Struts.Mybatis.Hibernate等开源框架,有了这些框架的诞生,平时的开发工作量也是变得越 ...
随机推荐
- 设计模式之——Template模板模式
Template模式又叫模板模式,是在父类中定义处理流程的框架,在子类中实现具体处理逻辑的模式.当父类的模板方法被调用时程序行为也会不同,但是,不论子类的具体实现如何,处理的流程都会按照父类中所定义的 ...
- mysql一个特殊的条件.字符串除以0的结果.
select * form user where username = ''/1; 一开始一看以为还是错误的语法.... 结果出来一堆结果.. 原来条件是 ''除以1 ''除以1 结果是什么 ...
- 搭建wordpress
https://www.themepark.com.cn/xcjxgwordpressdzdyglyd.html
- (3.15)常用知识-sql server分页
推荐使用row_number over()方法,或2012以上使用offset PageSize = PageNumber = 方法一:(最常用的分页代码, top / not in) UserId ...
- python collections模块 计数器(counter)
一.计数器(counter) Counter是对字典类型的补充,用于追踪值的出现次数. ps:具备字典的所有功能 + 自己的功能 把我写入的元素出现的多少次都计算出来 import collectio ...
- testng的@Factory及其与@DataProvider的区别
Factory,顾名思意就是工厂,也就是工厂方法,在软件开发中一般结合多态使用,用来根据不同的条件创建不同的类对象. 在这里,Factory一般用来创建一个测试类的多个实例,每个实例属性不同,以执行不 ...
- (转)Linux下配置tomcat+apr+native应对高并发
摘要:在慢速网络上Tomcat线程数开到300以上的水平,不配APR,基本上300个线程狠快就会用满,以后的请求就只好等待.但是配上APR之后,Tomcat将以JNI的形式调用Apache HTTP服 ...
- 简明python教程九----异常
使用try...except语句来处理异常.我们把通常的语句放在try-块中,而把错误处理语句放在except-块中. import sys try: s = raw_input('Enter som ...
- java通过URL获取文本内容
原文地址https://www.cnblogs.com/myadmin/p/7634262.html public static String readFileByUrl(String urlStr) ...
- 爬虫-Beautiful Soup模块
阅读目录 一 介绍 二 基本使用 三 遍历文档树 四 搜索文档树 五 修改文档树 六 总结 一 介绍 Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通 ...