点击查看AngularJS系列目录


依赖注入

一般来说,一个对象只能通过三种方法来得到它的依赖项目:

  1. 我们可以在对象内部创建依赖项目
  2. 我们可以将依赖作为一个全局变量来进行查找或引用
  3. 我们可以将依赖传递到需要它的地方

在使用依赖注入时,我们采用的是第三种方式(另外两种方式都会引起其他困难的挑战,例如污染全局作用域以及使隔离变得几乎不可能)。依赖注入是一种设计模式,它移除了硬编码依赖,因此使得我们可以在运行中随时移除并改变依赖项目。

在运行过程中能够修改依赖项目的能力允许我们创建隔离环境,这对于测试来说是非常理想的。我们可以用测试环境中的一个冒牌对象来替换生产环境中的一个真实对象。

从功能上来说,这种模式通过自动提前查找依赖以及为依赖提供目标,以此将依赖资源注入到需要它们的地方。

在我们编写一个依赖于其他对象或库的组件时,我们会描述它的依赖项目。再运行过程中,一个注入器将会创建依赖的实例并将它们传递给一个依赖消费者。

// 一个来自于AngularJS的好例子
function SomeClass(greeter) {
this.greeter = greeter;
}
SomeClass.prototype.greetName = function(name) {
this.greeter.greet(name)
}

注意:像上面的实例代码一样,在全局作用域中创建一个控制器永远不是一个好主意。上面的代码只是为了简化说明依赖注入的原理。

在运行中,SomeClass并不关心它是如何获得greeter依赖的,只要得到它就行。为了将greeter实例传递到SomeClass中,SomeClass的创造者还需要负责在创建函数时为它传递依赖。

基于以上原因,Angular使用$injector来管理依赖查询以及实例化依赖项目。事实上,$injector负责处理我们的Angular组件中所有的实例,包括我们应用的模块,指令,控制器等等。

在运行过程中,当我们的任何模块被引导启动时,注入器实际上负责实例化这个对象的实例并为它传递它所需要的依赖项目。

例如,下面的这段简单的代码声明了一个单独的模块和一个单独的控制器,如下所示:

angular.module('myApp', [])
.factory('greeter', function() {
return {
greet: function(msg) { alert(msg); }
}
})
.controller('MyController',
function($scope, greeter) {
$scope.sayHello = function() {
greeter.greet("Hello!");
};
});

在运行期间,当AngularJS初始化我们模型的实例时,它会查找greeter并简单地将它传递给我们的模块:

<div ng-app="myApp">
<div ng-controller="MyController">
<button ng-click="sayHello()">Hello</button> </div>
</div>

在幕后,Angular运行的过程如下所示:

// 使用注入器载入应用
var injector = angular.injector(['ng', 'myApp']); //和注入器一起载入$controller var $controller = injector.get('$controller');
var scope = injector.get('$rootScope').$new(); //载入控制器,将它传递给一个作用域
//这就是angular在运行过程中做的事
var MyController = $controller('MyController', {$scope: scope})

上面的例子中并没有描述我们怎样去寻找greeter;它运行非常简单,在此期间注入器会帮助我们找到并载入依赖项目。

在实例化期间,AngularJS使用一个注释函数来从传递过去的数组中提取属性。你可以在Chrome浏览器的开发者工具中输入以下代码来查看这个函数:

> injector.annotate(function($q,greeter){})
["$q","greeter"]

在每个Angular应用中,$injector都一直在运行,不管我们有没有意识到这一点。当我们在编写一个控制器但是没有加上[]括号标示符时,或者显式的设置了它们的时,$injector将会根据变量的名称来推测依赖项目。

通过推测来注释

Angular假设函数的参数名称就是依赖项目的名称,如果没有特别指明的话。因此,它会在函数上调用toString()方法,解析并从函数中提取变量,然后使用$injector将这个变量注入到对象的实例中。注入的过程是这样的:

injector.invoke(function($http,greeter){});

注意到这个过程只能在没有经过压缩,没有歧义的代码下面完成,因为Angular需要完整的解析变量。

通过JavaScript的推测,参数的顺序并不重要:Angular会为我们找出这些参数并将正确属性注入到“正确”的位置。

JavaScript精简器一般来说会将函数的参数变为个数最少的字母(同时也会改变空格,移除新行和注释等等),以此来减少JavaScript文件最终的体积。如果我们没有显式的描述变量,Angular将不能够推测变量,从而无法注入需要的依赖项。

显式注释

Angular为我们提供了一种方法来显式的定义一个函数所需要的依赖项目。这中方法允许精简器将函数的参数重命名,同时也能保证将合适的服务注入到函数中。

注入过程使用$inject属性来注释函数。一个函数的$inject属性是一个包含服务名称的数组,这些服务用作依赖项会被注入到函数中。

为了使用$inject属性方法,我们将它在一个函数或者函数名上进行设置。

var aControllerFactory =
function aController($scope, greeter) {
console.log("LOADED controller", greeter);
// ... 控制器
};
aControllerFactory.$inject = ['$scope', 'greeter'];
// Greeter 服务
var greeterService = function() {
console.log("greeter service");
}
// 我们的应用控制器
angular.module('myApp', [])
.controller('MyController', aControllerFactory)
.factory('greeter', greeterService);
// 获取注入器并创建一个新作用域
var injector = angular.injector(['ng', 'myApp']),
controller = injector.get('$controller'),
rootScope = injector.get('$rootScope'),
newScope = rootScope.$new();
// 调用控制器
controller('MyController', {$scope: newScope});

使用这种注释风格,顺序很重要,因此$inject数组必须要匹配注入的变量顺序。这种注入的方法可以在精简代码的情况下正常运行,因为注释信息依然会打包在函数中。

内联注释

Angular提供的最后一种注释的方法是内联注释。这种语法糖和上面提到的$inject注入方法运行方式相同,但是允许我们在函数定义的时候编写内联参数。另外,它允许我们在定义时不使用一个临时变量。

内联注释允许我们在定义一个Angular对象时传递一个参数数组而不是一个函数。数组中的元素是一个注入依赖项字符串的列表,最后一个变量则是对象的函数定义。

例如:

angular.module('myApp')
.controller('MyController',
['$scope', 'greeter', function($scope, greeter) {
}]);

内联注释方法可以在精简代码的情况下正常运行,因为我们在其中传递了一个字符串列表。我们经常将这个方法叫做方括号注释或者数组注释。

$inject API

尽管我们需要直接使用$injector的情况非常非常少,对$inject的API有一些了解会帮助我们更好的理解它的运行机制。

annotate()

annotate()函数会返回一个在初始化时会被注入到函数中的服务名称数组。annotate()函数通常在调用的时候被注入器用来决定应该注入什么服务。

annotate()函数会接收一个变量:

  • fn(函数或者数组)

fn可以是一个给定的函数,也可以是包含位于方括号标示符中的函数定义的数组。

annotate()函数会返回一个在初始化时会被注入到函数中的服务名称数组。

var injector = angular.injector(['ng', 'myApp']); injector.annotate(function($q, greeter) {});
// ['$q', 'greeter']

在你的Chrome浏览器的调试器中试验一下。

get()

get()方法接收一个参数同时会返回一个服务的实例。

  • name(字符串)

name变量是我们想要得到的实例的名称。

get()通过名称返回一个服务的实例。

has()

如果注入器知道它的注册服务中包含一个服务,has()方法会返回true,反之则返回false。它接收一个参数:

  • name(字符串)

这个字符串是我们想要在注入器的注册服务中查找的服务的名称。

instantiate()

instantiate()方法会创建一个JavaScript类型的新实例。它接收一个构造器函数并连同所有指定参数调用new操作符。它接收两个参数:

  • Type(函数)

这个函数是将要调用的注释构造器函数。

  • locals(对象 – 可选)

这个可选参数在函数被调用时提供了另外一种传递参数的办法。

instantiate()方法返回Type的一个新实例。

invoke

invoke()方法调用方法同时添加来自于$injector的方法参数。

这个invoke()方法接收三个参数:

  • fn(函数)

这个函数是将要被调用的函数。这个对于函数的参数连同注释一起被设置。

  • self(对象 – 可选)

self变量允许我们设置将要被调用方法的this变量。

  • locals(对象 – 可选)

这个可选参数在函数被调用时提供了另一种传递变量名称的方法。

ngMin

通过以上三种定义注释的方法,很重要的一点是注意到在定义一个函数的时候这些可选项都存在。然而,在具体生产过程中,刻意留心去关注参数顺序和代码膨胀是非常不方便的一件事。

ngMin工具为我们减轻了显示定义依赖项的痛苦。ngMin是一个针对Angular应用的预精简器。它会遍历我们的Angular应用并为我们设置依赖注入。

例如,它会将下面的代码:

angular.module('myApp', [])
.directive('myDirective',
function($http) { })
.controller('IndexController',
function($scope, $q) {
});

转化为下面的代码:

angular.module('myApp', [])
.directive('myDirective', [
'$http',
function ($http) { }
]).controller('IndexController',
[ '$scope',
'$q',
function ($scope, $q) {
} ]);

ngMin为我们节省了许多输入代码的时间,同时让我们的源文件变得非常干净。

安装ngMin

为了安装ngMin,我们需要使用npm包管理器:

npm install -g ngmin

如果我们使用Grunt,我们可以安装grunt-ngmin Grunt任务。如果我们使用Rails,我们可以使用Ruby gem ngmin-rails。

使用ngMin

我们可以在命令行工具单独模式下使用ngMin,只需要为它传递两个参数:input.js和output.js或者通过stdio/stdout,如下所示:

$ ngmin inpit.js output.js

或者

$ ngmin < input.js > output.js

在上面的例子中input.js是我们的源文件,output.js是注释输出文件。

ngMin是怎样运行的

从核心上来说,ngMin使用抽象语树(AST)来遍历JavaScript源文件。通过astral – 一个AST工具框架 – 的帮助,它使用必要的注释重建了源文件,并且使用escodegen输出了更新后的文件。

ngmin希望我们的Angular源文件由逻辑声明组成。如果我们的代码语法和本书中使用的代码语法相似,ngMin将能够解析源文件并对它进行预精简。

本文译自《ng-book》第十四章.  装载自: http://www.html-js.com/article/1887

详解AngularJS中的依赖注入的更多相关文章

  1. 【laravel5.*】详解laravel中的依赖注入

    1.下面这个是自定义的类,钉钉扫码登录web 网页授权OAuth2.0,是一个典型的依赖注入参考示例:

  2. 转: 理解AngularJS中的依赖注入

    理解AngularJS中的依赖注入 AngularJS中的依赖注入非常的有用,它同时也是我们能够轻松对组件进行测试的关键所在.在本文中我们将会解释AngularJS依赖注入系统是如何运行的. Prov ...

  3. 理解AngularJS中的依赖注入

    点击查看AngularJS系列目录 理解AngularJS中的依赖注入 AngularJS中的依赖注入非常的有用,它同时也是我们能够轻松对组件进行测试的关键所在.在本文中我们将会解释AngularJS ...

  4. 详解AngularJS中的filter过滤器用法

    系统的学习了一下angularjs,发现angularjs的有些思想根php的模块smarty很像,例如数据绑定,filter.如果对smarty比较熟悉的话,学习angularjs会比较容易一点.这 ...

  5. AngularJS中的依赖注入

    依赖注入 | Dependency Injection 原文链接: Angular Dependency Injection翻译人员: 铁锚翻译时间: 2014年02月10日说明: 译者认为,本文中所 ...

  6. Orchard详解--第三篇 依赖注入之基础设施

    Orchard提供了依赖注入机制,并且框架的实现也离不开依赖注入如模块管理.日志.事件等.在前一篇中提到在Global.asax中定义的HostInitialization创建了Autofac的IoC ...

  7. 详解Java Spring各种依赖注入注解的区别

    注解注入顾名思义就是通过注解来实现注入,Spring和注入相关的常见注解有Autowired.Resource.Qualifier.Service.Controller.Repository.Comp ...

  8. 使用IDEA详解Spring中依赖注入的类型(上)

    使用IDEA详解Spring中依赖注入的类型(上) 在Spring中实现IoC容器的方法是依赖注入,依赖注入的作用是在使用Spring框架创建对象时动态地将其所依赖的对象(例如属性值)注入Bean组件 ...

  9. Atitit js中的依赖注入di ioc的实现

    Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内  builder  即可..2 Service locator method走ok拦2 Jav ...

随机推荐

  1. SSE图像算法优化系列十一:使用FFT变换实现图像卷积。

    本文重点主要不在于FFT的SSE优化,而在于使用FFT实现快速卷积的相关技巧和过程. 关于FFT变换,有很多参考的代码,特别是对于长度为2的整数次幂的序列,实现起来也是非常简易的,而对于非2次幂的序列 ...

  2. js封装成插件-------Canvas统计图插件编写

    之前就说过,我想写一个canvas画统计图的插件,现在写好了 先说下实现的功能吧: 1.可以通过自定义X轴坐标属性和Y轴坐标属性按比例画出统计图 2.可以选择画折现图还是柱形统计图,或者两者都实现 3 ...

  3. Mysql 分区详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt120 一.什么是表分区通俗地讲表分区是将一大表,根据条件分割成若干个小表.m ...

  4. Project 5:替换指定字符串

    这个程序主要用于替换指定字符串,较为简单. #include <stdio.h> void change(char *,char *,char *); int ju(char *,char ...

  5. NHibernate教程(19) —— 一级缓存

    本节内容 引入 NHibernate一级缓存介绍 NHibernate一级缓存管理 结语 引入 大家看看上一篇了吗?对象状态.这很容易延伸到NHibernate的缓存.在项目中我们灵活的使用NHibe ...

  6. 【★】Web精彩实战之

    JS精彩实战之<智能迷宫>      ---宝贵编程经验分享会--- hello大家好,这里是Web云课堂,之前的一年里我们经历了Html和CSS的系统攻城,此时的你们已经是做静态(动静结 ...

  7. 【Beta阶段】计划安排

    一.新成员介绍 姓名    陈雄 学号    106 角色    前端 个人相片     二.完善功能 登录注册 记分板 排行榜 界面优化 三.新增功能 换肤(可以一试) 联网PK 分享邀请 四.团队 ...

  8. 201521044091 《Java学习笔记》 第六周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结.注1:关键词与内容不求多,但概念之间的联系要清晰,内容覆盖面 ...

  9. 201521123016《Java设计与程序》第6周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图,对面向对象思想进行一个总结. 2. 书面作业 1.clone方法 1.1 Object对 ...

  10. 201521123050 《Java程序设计》第4周学习总结

    1. 本周学习总结 2. 书面作业 1.注释的应用 1.1使用类的注释与方法的注释为前面编写的类与方法进行注释,并在Eclipse中查看.(截图) 2.面向对象设计(大作业1,非常重要) 2.1 将在 ...