详解AngularJS中的依赖注入
依赖注入
一般来说,一个对象只能通过三种方法来得到它的依赖项目:
- 我们可以在对象内部创建依赖项目
- 我们可以将依赖作为一个全局变量来进行查找或引用
- 我们可以将依赖传递到需要它的地方
在使用依赖注入时,我们采用的是第三种方式(另外两种方式都会引起其他困难的挑战,例如污染全局作用域以及使隔离变得几乎不可能)。依赖注入是一种设计模式,它移除了硬编码依赖,因此使得我们可以在运行中随时移除并改变依赖项目。
在运行过程中能够修改依赖项目的能力允许我们创建隔离环境,这对于测试来说是非常理想的。我们可以用测试环境中的一个冒牌对象来替换生产环境中的一个真实对象。
从功能上来说,这种模式通过自动提前查找依赖以及为依赖提供目标,以此将依赖资源注入到需要它们的地方。
在我们编写一个依赖于其他对象或库的组件时,我们会描述它的依赖项目。再运行过程中,一个注入器将会创建依赖的实例并将它们传递给一个依赖消费者。
// 一个来自于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中的依赖注入的更多相关文章
- 【laravel5.*】详解laravel中的依赖注入
1.下面这个是自定义的类,钉钉扫码登录web 网页授权OAuth2.0,是一个典型的依赖注入参考示例:
- 转: 理解AngularJS中的依赖注入
理解AngularJS中的依赖注入 AngularJS中的依赖注入非常的有用,它同时也是我们能够轻松对组件进行测试的关键所在.在本文中我们将会解释AngularJS依赖注入系统是如何运行的. Prov ...
- 理解AngularJS中的依赖注入
点击查看AngularJS系列目录 理解AngularJS中的依赖注入 AngularJS中的依赖注入非常的有用,它同时也是我们能够轻松对组件进行测试的关键所在.在本文中我们将会解释AngularJS ...
- 详解AngularJS中的filter过滤器用法
系统的学习了一下angularjs,发现angularjs的有些思想根php的模块smarty很像,例如数据绑定,filter.如果对smarty比较熟悉的话,学习angularjs会比较容易一点.这 ...
- AngularJS中的依赖注入
依赖注入 | Dependency Injection 原文链接: Angular Dependency Injection翻译人员: 铁锚翻译时间: 2014年02月10日说明: 译者认为,本文中所 ...
- Orchard详解--第三篇 依赖注入之基础设施
Orchard提供了依赖注入机制,并且框架的实现也离不开依赖注入如模块管理.日志.事件等.在前一篇中提到在Global.asax中定义的HostInitialization创建了Autofac的IoC ...
- 详解Java Spring各种依赖注入注解的区别
注解注入顾名思义就是通过注解来实现注入,Spring和注入相关的常见注解有Autowired.Resource.Qualifier.Service.Controller.Repository.Comp ...
- 使用IDEA详解Spring中依赖注入的类型(上)
使用IDEA详解Spring中依赖注入的类型(上) 在Spring中实现IoC容器的方法是依赖注入,依赖注入的作用是在使用Spring框架创建对象时动态地将其所依赖的对象(例如属性值)注入Bean组件 ...
- Atitit js中的依赖注入di ioc的实现
Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内 builder 即可..2 Service locator method走ok拦2 Jav ...
随机推荐
- The Karting 2017ccpc网络赛 1008
The Karting championship will be held on a straight road. There are N keypoints on the road. The pat ...
- Java面向对象的理解
Java是一门面向对象的编程语言(Object Oriented Programming,OOP), 这个句话是每个学习Java的程序员应该先深刻理解的一句话. 我们之所以将自自然界分解,组织成各种概 ...
- zoj3777 Problem Arrangement
The 11th Zhejiang Provincial Collegiate Programming Contest is coming! As a problem setter, Edward i ...
- python基础学习(十三)
re模块包含对 正则表达式.本章会对re模块主要特征和正则表达式进行介绍. 什么是正则表达式 正则表达式是可以匹配文本片段的模式.最简单的正则表达式就是普通字符串,可以匹配其自身.换包话说,正则表达式 ...
- mysql 插入字段 字符串
update hand_over set pay_info='{"waring_tip":"{\"pay_order_cancel\":0,\&qu ...
- 谈谈.NET,Java,php
开通博客后,一直都是转点别的朋友写的有意思的博文,今天我来写我在博客园的第一篇文章,说的不对的地方请你指正.希望本文能为一些准备学习编程的朋友有一点帮助. 开发桌面程序一直都是c语言,c++的天下,因 ...
- hdu 2066 最短路水题
题意:给出多个可选择的起始点和终点,求最短路 思路:执行起始点次的spfa即可 代码: #include<iostream> #include<cstdio> #include ...
- Django1.10主题指南—模型
模型是你的数据的唯一的.权威的信息源.它包含你所储存数据的必要字段和操作行为.通常,每个模型都对应着数据库中的唯一一张表. 基础认识: 每个model都是一个继承 django.db.models.M ...
- 转:【Java集合源码剖析】Java集合框架
转载轻注明出处:http://blog.csdn.net/ns_code/article/details/35564663 Java集合工具包位于Java.util包下,包含了很多常用的数据结构, ...
- 【Alpha】第五次Daily Scrum Meeting
GIT 一.今日站立式会议照片 二.会议内容 今天对昨天会议上产生的分歧进行了意见统一,每个人都阐述了自己的见解与看法,对,大家确实希望要做出挑礼物这样一个小程序就要尽力做到最好,但也对一些功能的实现 ...