依赖注入是一种软件设计模式,用来处理代码的依赖关系。
一般来说有三种方法让函数获得它需要的依赖:
- 它的依赖是能被创建的,一般用new操作符就行。
- 能够通过全局变量查找依赖。
- 依赖能在需要时被导入。
前两种方式都不是很好,因为它们需要对依赖硬编码,使得修改依赖的时候变得困难。特别是在测试的时候不好办,因为对某个部分进行孤立的测试常常需要模拟它的依赖。
第三种方式是最好的,因为它不必在组件中去主动寻找和获取依赖,而是由外界将依赖传入。
举个例子:
function SomeClass(greeter) { this.greeter = greeter } SomeClass.prototype.doSomething = function(name) { this.greeter.greet(name); }
上面的例子中SomeClass不关心去哪里获得叫greeter的依赖。这是我们想要的结果,但是这也同时把获取依赖的任务交给了负责创建SomeClass的代码。
每一个AngularJS应用都有一个注入器(injector)用来处理依赖的创建。注入器是一个负责查找和创建依赖的服务定位器。举个例子:
angular.module(‘myModule‘, []). // 告诉注入器如何来创建这个greeter // 注意这个greeter本身依赖‘$window‘ factory(‘greeter‘, function($window) { // 这个方法,我们叫做工厂方法,它的作用是用来创建这个greeter服务 return { greet: function(text) { $window.alert(text); } }; }) // 在模块myModule中,创建一个新的注入器 // (这经常在angular启动时自动完成) var injector = angular.injector(‘myModule‘); // 从这个注入器中得到greeter这个服务 var greeter = injector.get(‘greeter‘);
通过请求依赖的方式解决了硬编码的问题,但是同样也意味着注入器需要在应用中传递,这违反了迪米特法则。我们通过使用下面这个例子中声明的方式来将依赖查找都给注入器来解决。
<!-- Given this HTML --> <div ng-controller="MyController"> <button ng-click="sayHello()">Hello</button> </div> // And this controller definition function MyController($scope, greeter) { $scope.sayHello = function() { greeter(‘Hello World‘); }; } //‘ng-controller‘指令做了以下事情 injector.instantiate(MyController);
注意,通过使用ng-controller
来实例化控制器类,是的,控制器和注入器不再相关联,这是最好的结果。控制器中的代码可以简单的请求依赖而不必处理注入器。这种方式就没有破坏迪米特法则。
注入器怎么知道需要注入什么依赖呢?
注入器需要应用提供一些标记来表示自己需要的依赖。在关于AngularJS的某些API文档中你会看到函数都是被注入器调用的。注入器需要知道函数需要什么依赖。下面有三个等效的表示自己需要的依赖的方法。这些方法可以互相替换,并且是等效的。
(1)最简单的处理依赖的方法,就是假设函数的参数名就是依赖的名字
function MyController($scope, greeter) { ... }
给出一个注入器,可以通过检查函数声明来获取函数名,从而知道需要依赖的函数。在上面的例子中,$scope
和greeter
是需要注入到函数中的依赖。
坦白的来讲,用了这种方法就不能使用JavaScript minifiers/obfuscators
(一些用来缩短JS的类库)了,因为它们会改变变量名。
(2)要允许压缩类库重命名函数的参数,同时注入器又能正确处理依赖的话,函数需要使用$inject属性。这个属性是一个包含依赖的名称的数组。
var MyController = function(renamed$scope, renamedGreeter) { ... } MyController.$inject = [‘$scope‘, ‘greeter‘];
注意$inject标记里的值和函数声明的参数是对应的。
这种方式适合用于控制器的声明,因为控制器有了明确的声明标记。
(3)有时候用$inject标记不是很方便,比如用来声明指令的时候。
使用$inject会导致代码膨胀:
var greeterFactory = function(renamed$window) { ...; }; greeterFactory.$inject = [‘$window‘]; someModule.factory(‘greeter‘, greeterFactory);
这种情况我们就推荐使用第三种方式:
someModule.factory(‘greeter‘, [‘$window‘, function(renamed$window) { ...; }]);
依赖注入在AngularJS中很普遍,一般用在控制器和工厂方法中。
控制器中的依赖注入:
控制器是负责应用行为的类。推荐的控制器声明方法如下:
var MyController = function(dep1, dep2) { ... } MyController.$inject = [‘dep1‘, ‘dep2‘]; MyController.prototype.aMethod = function() { ... }
工厂方法的依赖注入:
工厂方法负责创建AngularJS中的大部分对象。比如指令,服务,过滤器。工厂方法一般在模块中使用,推荐的方法如下:
angular.module(‘myModule‘, []). config([‘depProvider‘, function(depProvider){ ... }]). factory(‘serviceId‘, [‘depService‘, function(depService) { ... }]). directive(‘directiveName‘, [‘depService‘, function(depService) { ... }]). filter(‘filterName‘, [‘depService‘, function(depService) { ... }]). run([‘depService‘, function(depService) { ... }]);
加油!