详解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 ...
随机推荐
- <经验杂谈>前端form提交导出数据
之前在做列表的是总会遇到一些导出的功能,而在做导出的时候总是习惯于用get的方法将参数放在url上,这样一来就会有很多的弊端,一是url的参数长度有限,遇到有的参数很长的时候就会报错,二是也不太安全. ...
- JQuery操作iframe父页面与子页面的元素与方法
JQuery操作iframe父页面与子页面的元素与方法 JQUERY IFRAME 下面简单使用Jquery来操作iframe的一些记录,这个使用纯JS也可以实现. 第一.在iframe中查找父页面元 ...
- About the diffrence of wait timed_wait and block in java
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * * @au ...
- vue echarts 遇到的bug之一 无法渲染的问题
图表示这样的 页面上有很多个图表,由于没有查询按钮,就只是点击发送ajax数据,所以把所有图表的方法放在updated中, 功能是实现了没问题,但是加载页面的时候会发送很多的ajax请求,而且点击修改 ...
- ios 初体验<UILabel控件>
创建控件: UILabel *label = [[UILabel alloc]init]; //设置控件大小 label.frame = CGRectMake(50,100,300,40);//分别为 ...
- 闭锁——CountDownLatch
一.概念 闭锁是一个同步工具类,主要用于等待其他线程活动结束后,再执行后续的操作.例如:在王者荣耀游戏中,需要10名玩家都准备就绪后,游戏才能开始. CountDownLatch是concurrent ...
- Project 2:传奇汉诺塔
汉诺塔简介:汉诺塔问题是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在 ...
- Cousera课程Learning How to Learn学习报告
花了三天完成了Cousera上的Learning how to learn的课程,由于未完成批阅他人作业,所以分不是很高,但是老师讲的课程非常的好,值得一听: 课程的笔记: 我们的一生是一个不断接触和 ...
- React——组件
一.创建组件 在React中有两种创建组件的方式,分别是函数形式的组件和类形式的组件 //函数形式: function Welcome(props){ return <p>this is ...
- 蓝桥杯试题利用数学知识经典解法,1.三个空瓶子换一瓶水;2.猜最后一个字母——猎八哥FLY
本博客为本人原创,转载请在醒目位置表明出处. 1.乐羊羊饮料厂正在举办一次促销优惠活动.乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下 去,但不允许赊账.请你计算一下,如果小明不浪 ...