本文源自:http://blog.csdn.net/woxueliuyun/article/details/50962645

学习之后略有所得, 来此分享。建议看原文。

模块是提供一些特殊服务的功能块,比如本地化模块负责文字本地化,验证模块负责数据验证。一般来说,服务在模块内部,当我们需要某个服务的时候,是先把模块实例化,然后再调用模块的方法。但Angular模块和我们通常理解的模块不一样,Angular模块只保留服务的声明,服务的实例化是由服务注入器完成的,实例化之后服务就留在了服务注入器中,和模块没有关系了,这就是为什么我们使用的服务全部来自注入器的原因。

每调用一次angular.boostrap()方法会创建一个新的Angular应用和一个新的服务注入器,因此,每个应用都对应一个服务注入器,彼此互不冲突。

在angular中,模块可以是对象、方法(如果是数组,数组的最后一个元素必须是方法,前面的元素都是方法按顺序排列的参数名称)。后面讲的模块属性和方法,都属于通过angular.module()定义的模块对象。如果模块是方法,是不需要经过angular.module()定义的,只需写入依赖数组(就是说依赖数组的元素可以是方法),模块在加载依赖关系的时候直接执行了。

注意:通过angular.module()方法定义的模块是唯一的,如果重复定义就会覆盖前面的定义。

angular模块

angular模块通过angular.module(name,requires, configFn)方法生成:

  • 参数name是模块名称;
  • 参数requires表示依赖模块数组。如果不设置requires参数,调用angular.module(name)方法表示获取这个模块;因此,如果确定新模块没有依赖关系,必须设置requires为空数组[];
  • 参数configFn是方法或数组,负责在模块初始化时做一些配置,如果是数组,最后一个元素必须是方法。

方法configFn并不是在执行angular.module()的时候立即执行,而是当这个模块被第一次使用时,由注入器调用执行。同时,查看方法configFn中的this就会发现,这个this在浏览器中指向的是window,而不是module。而且,方法configFn只会执行一次,因此同一个angular模块不会重复配置。

参数requires中的字符串表示依赖的模块名称。如果不是字符串,则必须是方法(或数组格式的方法),那么,这个方法就代表了一个模块。

同名模块

已经初始化的angular模块保存在一个叫modules的缓存对象中,key是模块名,value是模块对象。所以,定义一个同名的模块,等于覆盖之前的模块。

服务注入

前面已经讲了,angular模块只保留服务的定义,现在我们再来理解服务是如何加入注入器的。

在了解服务注入器前,还需要了解另一个概念,就是服务提供商,在Angular中称为Provider,几乎所有的服务(除了$injector)都是由服务提供商供应。无论是服务还是服务提供商,他们在Angular中都是唯一的,服务和服务提供商是一个一对一的关系。也正是因为这个关系,我们在使用模块service()、value()等方法时,感觉我们定义的好像只是服务,但其实Angular背后为这些服务都创建了一一对应的服务提供商。注入器的机制就是当我们需要某个服务的时候,首先根据服务名找到对应的服务提供商,然后由服务提供商创建对应的服务并返回。

所以这就是整个过程:

  1. 模块定义服务、服务提供商;
  2. 注入器根据模块依赖关系加载模块,实例化所有服务提供商;
  3. 应用需要服务,注入器根据服务名寻找服务提供商,服务提供商实例化服务。

以上只是理论,现在从代码层面来看Angular是如何实现的。

每个angular模块内置有三个数组,invokeQueue保存如何注入服务提供商和值的信息;configBlocks保存模块的配置信息;runBlocks保存这个模块的执行信息。模块被使用的时候,注入器根据invokeQueue中的信息,实例化服务提供商;根据configBlocks中的信息对服务提供商做一些额外的处理;根据runBlocks中提供的信息,调用前面的服务提供商提供的服务执行模块需要完成的工作。

angular模块提供了很多方法来填充这三个数组,比如config()、run()等。三个数组的元素也是数组,具体元素格式参考后面的说明。

调用队列 – invokeQueue

数组元素为[‘provider’, ‘method’, arguments]。

举例– 添加一个Controller:

 angular.module('ngAppDemo',[])  

 .controller('ngAppDemoController',function($scope) {  

       $scope.a= 1;  

       $scope.b = 2;  

 });  

这段代码等于:

invokeQueue.push(['$controllerProvider','register', ['ngAppDemoController', function(){}]]);

注入器根据这个信息,就会调用$controllerProvider的register方法注册一个ngAppDemoController。  

angular模块实例属性和方法

属性

requires

表示模块的依赖数组,即angular.module方法的requires参数。

name

模块的名字。

_invokeQueue、_configBlocks、_runBlocks

分别对应invokeQueue、configBlocks、runBlocks。

方法

模块的以下方法最后全部会返回模块实例本身,形成执行链

animation()

调用这个方法表示这个模块将在$animateProvider中注册一个动画服务。

原理:在invokeQueue尾部插入['$animateProvider', 'register', arguments]。

config()

调用这个方法,表示给这个模块追加一个配置方法。

原理:在configBlocks尾部插入['$injector','invoke', arguments]。

constant()

调用这个方法表示这个模块将给默认的$provider注册一个常量。

原理:在invokeQueue首部插入['$provide', 'constant', arguments]。

controller()

调用这个方法表示模块将在$controllerProvider中注册一个控制器。

原理:在invokeQueue尾部插入['$controllerProvider', 'register', arguments]。

directive()

调用这个方法表示这个模块将在$compileProvider中注册一个指令。

原理:在invokeQueue尾部插入['$compileProvider', 'directive', arguments]。

factory()

调用这个方法表示这个模块中将生成一个服务工厂(隐式创建一个了服务提供商)。

原理:在invokeQueue尾部插入['$provide', 'factory', arguments]。

filter()

调用这个方法表示这个模块将在$filterProvider中注册一个过滤器。

原理:在invokeQueue尾部插入['$filterProvider', 'register', arguments]。

provider()

调用这个方法表示这个模块将添加一个服务提供商。

原理:在invokeQueue尾部插入['$provide', 'provider', arguments]。

run(block)

调用这个方法表示这个模块将执行某个功能块,block可以是方法,也可以是数组。

原理:在invokeQueue尾部插入block。

service()

调用这个方法表示这个模块将注册一个服务(隐式创建了一个服务提供商)。

原理:在invokeQueue尾部插入['$provide', 'service', arguments]。

value()

调用这个方法表示这个模块将注册一个变量(隐式创建了一个服务提供商)。

原理:在invokeQueue尾部插入['$provide', 'value', arguments]。

服务注入器(Service Injector) & 服务提供商(Service Provider)

在Angular中,服务可能是对象、方法、或者一个常量值。服务由服务提供商创建,而服务提供商由注入器统一管理。当我们需要某个服务的时候,注入器负责根据服务名寻找相应的服务提供商,然后由服务提供商的$get()生产工厂创建服务实例。因此,服务提供商必须有一个$get()方法,这个方法就是服务创建单例工厂。

背后原理:注入器中的Providers和Services各自通过一个Map对象保存在缓存(分别对应providerCache和instanceCache)中,只不过Providers的key是serviceName + “Provider”,而Services的key是serviceName。

服务提供商-Provider

Provider即服务提供商,必须有一个$get()方法,$get()的返回值是Provider在注入器中实际的服务。

注入器

创建注入器的方法只在bootstrap()方法中被调用过,也就是说,每一个angular应用对应一个注入器。

注入过程

注入器由angular.injector(modulesToLoad, isStrictDi)方法创建,在angular中其实为createInjector方法。参数modulesToLoad是数组,元素格式为以下之一:

  • ‘module’,模块的名称。
  • [‘service1’, ‘service2’, fn]。
  • fn,方法的返回值必须仍然是方法。

方法angular.injector()的执行过程:

1.        遍历modulesToLoad,根据moduleName寻找或生成相应的模块。

2.        调用模块invokeQueue中的所有方法,这一步的目的是创建必需的服务。

3.        调用模块configBlocks中的所有方法,目的是用已有的服务配置这个模块。

4.        调用注入器的invoke()方法执行模块runBlocks的所有方法。

5.        返回服务注入器实例。

不是所有模块都是对象,如果模块本身是方法或者是数组(最后一个元素必须是方法),则运行这个方法、或数组的最后一个方法,相当于直接进入了第四步。

注入器与bootstrap的关系

创建注入器的方法在angular.js中只在bootstrap()方法中被调用过,也就是说,每一个angular应用对应一个注入器。

注入器方法

在providerCache中和instanceCache中分别内置有一个$injector对象,分别负责给模块注入服务提供商和为方法注入服务。一般我们只谈论instanceCache中的$injector对象,因为providerCache和它的$injector是私有的,只在Angular内部代码使用。

比如,执行模块调用队列、配置队列中的方法时注入的是服务提供商,而当调用运行队列中的方法时,注入的是服务。

$injector.invoke(fn, self, locals, serviceName)

执行方法fn。locals是可选参数,是对象,表示局部变量。self是fn中的this。

最后一个参数serviceName是可选参数,表示在哪个服务中调用了fn方法,用于错误信息显示,没有处理逻辑。

$injector.instantiate(Type, locals, serviceName)

l  如果参数Type为方法,根据Type的prototype创建一个实例(通过Object.create方法创建),如果Type是数组,使用最后一个元素的prototype。

l  参数Locals是当Type方法的参数出现在locals对象中的时候,取locals[arg]的值重新作为Type的参数。如果locals中没有,则等价于调用get(arg,serviceName)获取service作为新的参数。

实例化过程可以简单概括为

Type.apply(Object.create(Type.prototype),locals[argName]|| get(argName, serviceName))

注意:实例化出来的不一定是对象,也可能是方法。

最后一个参数serviceName是可选参数,表示在哪个服务中实例化了Type,用于错误信息显示,没有处理逻辑。

$injector.get(name, caller)

从注入器中获取一个服务实例。

参数name是服务的名称。参数caller也是字符串,表示调用这个服务的是哪个方法,用于错误信息提示,没有处理逻辑。

$injector.annotate(fn[,strictDi][,name] )

返回数组,数组的元素是fn方法需要注入的依赖服务。

在严格模式下,方法的依赖注入必须使用显示的注解加入,也就是说通过fn.$injector能够获取这个方法的依赖注入。

参数name是可选的,用于错误显示,没有处理逻辑。

方法annotate()也可以接受数组,数组的最后一个参数一定是fn,前面的元素则是依赖。

$injector.has(name)

检查该注入器中是否存在指定的服务。

Provider方法

注入器的providerCache中内置有一个$provider对象,这是注入器的默认服务提供商,$provider有六个固定的方法。这几个方法的作用主要是为注入器添加其他服务提供商。

注意:

  • 以下所有方法的name参数不需要以“Provider”结尾,因为provider()方法会默认把这个后缀加上。
  • 以下任何一个方法不做同名判断,因此,如果出现同名,后者将覆盖前者。

$provider.provide(name, provider)

参数provider可以是方法或数组,也可以是对象。

l  如果是方法,则是provider的构造函数。调用注入器的instantiate()方法,生成一个provider实例,并以name为key保存在注入器的providerCache中。

l  如果是数组,最后一个必须是provider的构造函数,前面的就是构造函数的参数名。之后的原理和provider是方法的情形相同。

l  如果是对象,说明这个provider已经被实例化了,只需有$get()方法即可。

$provider.factory(name, factoryFn, enforce)

使用$provider.provide()一般需要定义一个Provider类,如果不想定义Provider类,而是直接定义服务工厂,就可以使用这个方法。

背后原理:首先生成一个匿名对象,这个对象的$get属性就是factoryFn(enforce为false的情况下),然后把这个匿名对象作为$provider.provide()方法的第二个参数。所以,factoryFn其实依然是绑定在一个provider上的。

$provider.service(name, constructor)

调用injector.instantiate()方法,利用参数constructor生成service实例,参数name是这个service的名称。

众所周知,service由provider提供,那这个方法是怎么回事?原理:Angular首先根据constructor生成一个factoryFn,然后调用$provider.factory(name, factoryFn)。所以其实还是生成了一个provider。

举例:

$provider.service('filter', constructor)

等于创建了一个filter服务实例,并且在providerCache中保存了一个名称为“filterProvider”的服务提供商。

$provider.value(name, value)

这个方法实际调用injector.factory(name,valueFn(value), false)方法实现。所以其实等于创建一个只提供值的服务提供商。

$provider.constant(name, value)

这个方法直接在providerCache中添加一个属性实现。

$provider.decorate(serviceName, decorFn)

修改旧的服务,改为执行decorFn方法,并把servcieName原来的服务作为一个参数,参数名为$delegate。等价于一个静态代理。

背后原理:首先根据seviceName找到Provider,然后修改provider的$get属性。

服务实例化

所有服务都由Provider管理,除了常量值,其它一般都是还没有创建出来的。了解了注入器的所有方法,也应该可以看出,只有两个方法:get()和invoke()真实的调用了服务。其实,服务的实例化实际是在注入器的get()方法中完成的,而invoke()只是在需要的时候调用了get()。

angular内置模块

ngLocale - 本地化模块

angular.module('ngLocale',[]).provider('$locale', $LocaleProvider);

结果是invokeQueue.push(['$provide', 'provider',['$locale', $LocaleProvider]]);

ng

angular.module('ng',['ngLocale']).config(['$provide', function(){}]);

结果是configBlocks.push(['$injector', 'invoke',['$provide', function(){}]])。

三个固定模块

每个使用bootstrap(element, modules, config)生成的应用,注入器中有三个固定的模块:

  • 第一个模块是"ng"。
  • 第二个模块是[‘$provider’,fn],它的作用是把根元素element作为变量保存在$provider中。
  • 第三个模块是[‘$compileProvider’,fn],它的作用是根据config.debugInfoEnabled调用 $conpileProvider.debugInfoEnabled(true)。

AngularJS系统学习之Module(模块)的更多相关文章

  1. AngularJS系统学习之Scope(作用域)

    本文出自:https://www.w3ctech.com/topic/1611 看完了没怎么懂,  也许是和别人 原文作者: Nicolas Bhttps://www.w3ctech.com/topi ...

  2. AngularJS系统学习之Factory,Service, Provider(工厂,服务,供应者)

    本文转自:http://blog.csdn.net/zcl_love_wx/article/details/51404390 我看过敲过代码之后, 有了很深的理解, 这三个东西其实都是用来返回对象的. ...

  3. AngularJS系统学习之Directive(指令)

    本文转自https://www.w3ctech.com/topic/1612 原文作者: Nicolas Bevacqua 原文:AngularJS’ Internals In Depth, Part ...

  4. AngularJS系统学习之$watch(监控)

    在scope的内置的所有函数中,用的最多的可能就是$watch函数了, 当你的数据模型中某一部分发生变化时,$watch函数可以向你发出通知. 你可以监控单个对象的属性,亦可以监控需要经过计算的结果( ...

  5. AngularJS进阶(四十)创建模块、服务

    AngularJS进阶(四十)创建模块.服务 学习要点 使用模块构架应用 创建和使用服务 为什么要使用和创建服务与模块? 服务允许你打包可重用的功能,使之能在此应用中使用. 模块允许你打包可重用的功能 ...

  6. Python3学习笔记25-logging模块

    logging模块,Python自带用来记录日志的模块. 因为工作需要用到关于日志的,最近一直都在看关于日志模块的东西,百度了很多文章,可惜都是看的让人一头雾水,最后运气不错,找到一篇很详细的文章.传 ...

  7. Dubbo -- 系统学习 笔记 -- 配置

    Dubbo -- 系统学习 笔记 -- 目录 配置 Xml配置 属性配置 注解配置 API配置 配置 Xml配置 配置项说明 :详细配置项,请参见:配置参考手册 API使用说明 : 如果不想使用Spr ...

  8. Dubbo -- 系统学习 笔记 -- 配置参考手册

    Dubbo -- 系统学习 笔记 -- 目录 配置参考手册 <dubbo:service/> <dubbo:reference/> <dubbo:protocol/> ...

  9. 【js类库AngularJs】学习angularJs的指令(包括常见表单验证,隐藏等功能)

    [js类库AngularJs]学习angularJs的指令(包括常见表单验证,隐藏等功能) AngularJS诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀 ...

随机推荐

  1. 为公司做crm资产管理

    一.实现会议室预定 二.实现调查问卷 三.项目背景初始化分析 四.简单的登陆注册 五.学生管理 六.老师管理 七.销售管理 八.客户关系管理 九.抢单管理 十.微信发消息发邮件管理 补充:数据表设计. ...

  2. java操作redis学习(一):安装及连接

    文章参考自:http://www.cnblogs.com/edisonfeng/p/3571870.html,在此基础上进行了修改. 一.下载安装Redis redis官网显示暂时不支持Windows ...

  3. 关于mongodb副本集读写分离 及 日志切换

    mongodb的读写分离使用Replica Sets来实现 对于replica set 中的secondary 节点默认是不可读的.在写多读少的应用中,使用Replica Sets来实现读写分离.通过 ...

  4. eclipse中run as无run as server选项的解决方案

    在项目->右击->Properties->Project Facets->Modify Project,选择Java和DynamicWeb Module

  5. 多媒体开发之---h.264 SPS PPS解析源代码,C实现一以及nal分析器

    http://blog.csdn.net/mantis_1984/article/details/9465909 http://blog.csdn.net/arau_sh/article/detail ...

  6. bjfu1332 简单动规

    挺简单的动态规划题.我用记忆化搜索打的.直接上代码: /* * Author : ben */ #include <cstdio> #include <cstdlib> #in ...

  7. live555中fDurationInMicroseconds的计算

    live555中fDurationInMicroseconds表示单个视频或者音频帧所占用的时间间隔,也表示在fDurationInMicroseconds微秒时间后再次向Source进行getNex ...

  8. JQery 动态填充数据到table 中

    说明: 1.把数据库中的数据查询出来,填充到前台的table中,注意 从数据查询出来的 属性IsNew="0"(table 行tr的属性) 2.单击“添加”按钮 新添加行追加到ta ...

  9. A Short Introduction to Boosting

    http://www.site.uottawa.ca/~stan/csi5387/boost-tut-ppr.pdf Boosting is a general method for improvin ...

  10. FireMonkey 结构性初略分析

    Delphi 下的FireMonkey,很好地实现了 DirectUI与跨平台.学习了解他,对DirectUI编程及项目的跨平台实现有一定帮助.虽然作为开发者个体,并不需要了解太多这些东西,只要求拿来 ...