AngularJS基础教程

控制器与依赖注入

前言

Controller(控制器)是连接 Model 与 View 之间的纽带。
在 MV* 模式中,Model 与 View 是相对独立的,即低耦合的。为了在 Model 和 View 之间建立联系,可以使用 Controller。
“依赖注入”是一种设计理念,可以使用开发者在使用某个对象时不用关心其是如何实现的,也不用自己去创建这个对象,只需告诉系统我现在需要使用某个对象。这种理念可降低代码之间的耦合度。

之前绑定数据的方法

在之前的章节中我们是这样来绑定数据的:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>控制器与依赖注入</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="">
<input type="text" ng-model="name" />
<div ng-bind="name"></div>
</div>
</body>
</html>

这里用 ng-model 使一个文本框元素中输入的值为成一个 Model,然后在其它元素中使用 ng-bind 来绑定这个 Model。

但很多时候我们的数据并不是来自页面上的某个元素,比如我们通过 Ajax 从服务器取回的一个用户的个人信息。这种时候就可以用到 Controller。

使用Controller的案例

将上面的代码修改一下,使其成为下面这个样子:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>控制器与依赖注入</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myController">
<div ng-bind="name"></div>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.name = 'Hello AngularJS';
});
</script>
</body>
</html>

在浏览器中查看:

在这个案例中,在 HTML 部分并没有用 ng-model 定义 Model,但 ng-bind 仍然绑定到了数据(因为我们看到在页面上有输出“Hello AngularJS”)。那么这个数据是从哪里来的呢?

需要注意的是,与前面的案例不如,在这个案例中我们给 ng-app 设了一个值(“myApp”)。

来看看 Javascript 部分。首先用 angular.module() 方法定义了一个模块,并将定义的模块赋值给了变量 app

该方法有两个参数。第一个参数为模块的名字(myApp),即 HTML 代码中 ng-app 的值。第二个参数为该模块所需依赖的模块,它是一个数组,我们暂时不需要用到,所以先不管它,写一个空数组即可(第二个参数如果不传的话会报错)。

这段代码告诉 AngularJSng-app="myApp" 所在元素包含的所有内容当作一个模块来处理。

接下来,调用 appcontroller() 方法为 app 模块实现了一个控制器。

该方法有两个参数。第一个参数为该 Controller 的名字,它与 HTML 代码中的 ng-controller 相对应。第二个参数是一个 function,它是 Controller 的具体实现。

Controller 用来控制页面中所显示的数据,它是使用参数 $scope 来控制的。

在我们这个案例中,给 $scope 设了一个属性 name,并为它赋值为“Hello AngularJS”,然后这个值就显示在网页上了。

那么,$scope 是什么呢?$scope 是一个作用域

每个 Controller 都有它自己的作用范围,该作用范围是由 ng-controller 来决定的。ng-controller 所在的标签所包含的范围即是该 Controller 的作用域。

$scope.name = 'Hello AngularJS' 相当于在作用域内定义了一个变量,该变量的名字是“name”,并为它赋值为“Hello AngularJS”。那么,在 ng-controller 所在的标签所包含的范围内都可以使用这个变量,也就可以用 ng-bind 来绑定这个变量。

看看下面这段代码:

window.name = 'Amanda';

这段代码为 window 设置了一个属性,相当于在 window 的作用域(全局)内定义了一个变量 name$scope.name = 'Hello AngularJS' 与之类似,只不过它的范围不是全局的。

看看在作用域之外是否可绑定作用域之内定义的数据

前面说过,$scope 中数据只在其对应的 ng-controller 标签所包含的范围内有效,我们在 ng-controller 所在标签的外面绑定 $scope 中的数据看看:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>控制器与依赖注入</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myController">
<div ng-bind="name"></div>
</div>
<div ng-bind="name"></div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myController', function ($scope) {
$scope.name = 'Hello AngularJS';
});
</script>
</body>
</html>

在浏览器中查看,我们发现第二个 DIV 并没有如我们所期望的那样显示“Hello AngularJS”,这表明第二个 DIV 的 data-bind 并没有绑定到数据。这也正表明了 $scope 中的数据是在其对应的 ng-controller 所在标签的外部是无法访问的。

多个控制器的例子

一个模块可以有多个控制器,并且每个控制器有其自己的作用范围。看下面这个例子:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>控制器与依赖注入</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="ctrl0">
<div ng-bind="name"></div>
</div>
<div ng-controller="ctrl1">
<div ng-bind="name"></div>
</div>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('ctrl0', function ($scope) {
$scope.name = 'Hello';
});
app.controller('ctrl1', function ($scope) {
$scope.name = 'AngularJS';
});
</script>
</body>
</html>

在 HTML 部分分别为两个 DIV 设置了 ng-controller,在 Javascript 部分也对应地为 app 模块实现了两个 Controller,为这两个 Controller 的 $scope 都定义了一个 name 属性。

在浏览器中查看:

从运行结果可以看出,两个 $scope 并不会相互影响。

嵌套控制器的例子

多个 Controller 可以是并列的(比如上面的例子),也可以是嵌套的,即一个 Controller 包含在另一个 Controller 之内。看下面这个例子:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>控制器与依赖注入</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="ctrl0">
<div ng-bind="name"></div>
<div ng-bind="age"></div>
<div ng-controller="ctrl1">
<div ng-bind="name"></div>
<div ng-bind="age"></div>
</div>
</div>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('ctrl0', function ($scope) {
$scope.name = 'Hello';
$scope.age = 20;
});
app.controller('ctrl1', function ($scope) {
$scope.name = 'AngularJS';
});
</script>
</body>
</html>

在 Javascript 代码中,为父 Controller 的 $scope 定义了两个属性(name 和 age),为子 Controller 的 $scope 只定义了一个属性(name)。

但是在 HTML 代码中,父 ng-controller 和子 ng-controller 的范围内都有绑定 name 和 age。

在浏览器中查看:

可以看到,虽然在子 Controller 中的 $scope 上没有定义 age,但是子 ng-controller 内绑定的 age 却显示有数据,并且是在父 Controller 的 $scope 上定义的 age 的值。

由此我们可以得出,如果在子 ng-controller 内绑定的数据在子 Controller 的 $scope 中没有定义,则会使用父 Controller 的 $scope 中同名的数据。

依赖注入

在上面的例子中,Controller 是这样定义的:

app.controller('ctrl0', function ($scope) {
// .....
});

注意到 controller() 方法的第二个参数没?它是一个 function,为 Controller 的具体实现。在这个例子中,它有一个名为 $scope 的参数。

在一般的概念里,一个 function 的参数的名字是可以任意命名的,那么我们将 $scope 改为 oneParam 看看:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>控制器与依赖注入</title>
<script src="js/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myController">
<div ng-bind="name"></div>
</div>
<script type="text/javascript">
var app = angular.module('myApp', []);
app.controller('myController', function (oneParam) {
oneParam.name = 'Hello AngularJS';
});
</script>
</body>
</html>

在浏览器中查看,网页上并没有如我们期望的那样显示出“Hello AngularJS”,打开浏览器的控制台,发现报错了:

由此我们得知,参数 $scope 的名字是不能改变的,它必须是“$scope”。

可是,这不是与我们以前所学到的知识点相矛盾了吗?

好吧,抛开你固有的思维模式吧!这是一种“依赖注入”(Injector)的用法。

所谓“依赖注入”,也就是当需要使用某个对象时,只需以“声明式”的方式告诉 AngularJS:我现在要用某个对象了,AngularJS 就会帮你创建好这个对象对将它给你送过来,而不需要自己用代码去创建。

在上面这个例子中,function 的参数 $scope 就是告诉 AngularJS:我现在要用 $scope 这个对象了,然后 AngularJS 就将 $scope 送了过来。

我们知道,在 Javascript 中没有“反射”的概念,那么 AngularJS 是如何在 Javascript 中实现“依赖注入”的呢?

看看下面的例子:

var app = {
c0: function(){ // 在 app 里定义了一个名为 c0 的 class
this.name = 'Amanda';
},
execAFun: function(fn){ // 此 function 负责执行另一个 function
// 将要执行的 function 转为 String,
// 如果被执行的 function 是这样定义的:
// function(a, b){ .... }
// 调用它的 toString() 方法,得到的字符串应该是:“funtion(a, b){ .... }”。
// 利用 function 的这个特性,就能得到被执行的 function 的参数的名字
var str = fn.toString();
str = str.substring(str.indexOf('(') + 1, str.indexOf(')')).replace(/\s/g, '');
var ary = str.split(','); // 将所有参数的名字取出来放入了一个数组
// 依据参数的名字找到其对应的 class,实例化之后传给了要执行的 function
fn.apply(null, ary.map(function(item){
return new app[item]();
}));
}
};

app.execAFun(function(c0){
console.log(c0.name);
});

最后,来个广告

若你觉得此文不错,请分享,若认为尚需改进,请点讚。

结束语