Angular学习笔记(8)—依赖注入

一个对象通常有三种方式可以获得对其依赖的控制权:

  • 在内部创建依赖
  • 通过全局变量进行引用
  • 在需要的地方通过参数进行传递

依赖注入是通过第三种方式实现的。其余两种方式会带来各种问题,例如污染全局作用域,使隔离变得异常困难等。依赖注入是一种设计模式,它可以去除对依赖关系的硬编码,从而可以在运行时改变甚至移除依赖关系。
从功能上看,依赖注入会事先自动查找依赖关系,并将注入目标告知被依赖的资源,这样就可以在目标需要时立即将资源注入进去。
在编写依赖于其他对象或库的组件时,我们需要描述组件之间的依赖关系。在运行期,注入器会创建依赖的实例,并负责将它传递给依赖的消费者。
基于以上原因,AngularJS使用$injetor(注入器服务)来管理依赖关系的查询和实例化。事实上,$injetor负责实例化AngularJS中所有的组件,包括应用的模块、指令和控制器等。
在运行时,任何模块启动时$injetor都会负责实例化,并将其需要的所有依赖传递进去。

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>

而在内部,AngularJS的处理过程是下面这样的:

// 使用注入器加载应用
var injector = angular.injector(['ng', 'myApp']);
// 通过注入器加载$controller服务:var $controller = injector.get('$controller');
var scope = injector.get('$rootScope').$new();
// 加载控制器并传入一个作用域,同AngularJS在运行时做的一样
var MyController = $controller('MyController', {$scope: scope})

上面的代码中并没有说明是如何找到greeter的,但是它的确能正常工作,因为$injector会负责为我们查找并加载它。
AngularJS通过annotate函数,在实例化时从传入的函数中把参数列表提取出来。

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

在任何一个AngularJS的应用中,都有$injector在进行工作,无论我们知道与否。当编写控制器时,如果没有使用[]标记或进行显式的声明,$injector就会尝试通过参数名推断依赖关系。

推断式注入声明

如果没有明确的声明,AngularJS会假定参数名称就是依赖的名称。因此,它会在内部调用函数对象的toString()方法,分析并提取出函数参数列表,然后通过$injector将这些参数注入进对象实例。注入的过程如下:

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

这个过程只适用于未经过压缩和混淆的代码,因为AngularJS需要原始未经压缩的参数列表来进行解析。
有了这个根据参数名称进行推断的过程,参数顺序就没有什么重要的意义了,因为AngularJS会帮助我们把属性以正确的顺序注入进去。

显式注入声明

AngularJS提供了显式的方法来明确定义一个函数在被调用时需要用到的依赖关系。通过这种方法声明依赖,即使在源代码被压缩、参数名称发生改变的情况下依然能够正常工作。
可以通过$inject属性来实现显式注入声明的功能。函数对象的$inject属性是一个数组,数组元素的类型是字符串,它们的值就是需要被注入的服务的名称。

var aControllerFactory=function aController($scope,greeter) {
    console.log("LOADED controller", greeter);
    // ……控制器
};
aControllerFactory.$inject = ['$scope', 'greeter']; // Greeter服务
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数组元素的顺序必须和注入参数的顺序一一对应。这种声明方式可以在压缩后的代码中运行,因为声明的相关信息已经和函数本身绑定在一起了。

行内注入声明

行内注入声明同前面提到的通过$inject属性进行注入声明的原理是完全一样的,但允许我们在函数定义时从行内将参数传入。此外,它可以避免在定义过程中使用临时变量。
在定义一个AngularJS的对象时,行内声明的方式允许我们直接传入一个参数数组而不是一个函数。数组的元素是字符串,它们代表的是可以被注入到对象中的依赖的名字,最后一个参数就是依赖注入的目标函数对象本身。

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

由于需要处理的是一个字符串组成的列表,行内注入声明也可以在压缩后的代码中正常运行。通常通过括号和声明数组的[]符号来使用它。

$injector API

annotate()

annotate()方法的返回值是一个由服务名称组成的数组,这些服务会在实例化时被注入到目标函数中。annotate()方法可以帮助$injector判断哪些服务会在函数被调用时注入进去。
annotate()方法可以接受一个参数:fn(函数或数组),参数fn可以是一个函数,也可以是一个数组。annotate()方法返回一个数组,数组元素的值是在调用时被注入到目标函数中的服务的名称。

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

get()

get()方法返回一个服务的实例,可以接受一个参数:name(字符串),参数name是想要获取的实例的名称。get()根据名称返回服务的一个实例。

has()

has()方法返回一个布尔值,在$injector能够从自己的注册列表中找到对应的服务时返回true,否则返回false。它能接受一个参数:name(字符串),参数name是我们想在注入器的注册列表中查询的服务名称。

instantiate()

instantiate()方法可以创建某个JS类型的实例。它会通过new操作符调用构造函数,并将所有参数都传递给构造函数。它可以接受两个参数。

  • Type(函数):构造函数。
  • locals(对象,可选):这是一个可选的参数,提供了另一种传递参数的方式。

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

invoke()

invoke()方法会调用方法并从$injector中添加方法参数。
invoke()方法接受三个参数。

  • fn(function):这个函数就是要调用的函数。这个函数的参数由函数声明设置。
  • self (object-可选):self参数允许我们设置调用方法的this参数。
  • locals (object-可选):这个可选参数提供另一种方式在函数被调用时传递参数名给该函数。

invoke()方法返回fn函数返回的值。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 出于内存占用和性能的考虑,控制器只会在需要时被实例化,并且不再需要就会被销毁。这意味着每次切换路由或重新加载视图时...
    oWSQo阅读 498评论 0 1
  • 1、angularjs的几大特性是什么? 双向数据绑定、依赖注入、模板、指令、MVC/MVVM 2、列举几种常见的...
    秀才JaneBook阅读 1,537评论 0 22
  • AngularJS是什么?AngularJs(后面就简称ng了)是一个用于设计动态web应用的结构框架。首先,它是...
    200813阅读 1,583评论 0 3
  • 今天在西安火车站广场看见一个稚气未脱的少女,背着一个大六弦吉他,吉他包上手写着:rock is my bo...
    寒江独钓客阅读 255评论 0 2
  • 寒衣节是祭祖的日子,我领着爱人一起去公墓祭奠先祖。这一天来祭奠的人很多,骑车的、开车的,真可谓车水马龙。每到鬼节附...
    相南山人阅读 375评论 0 3