当HTML页面中出现ng-app和ng-controller指令时,AngularJS会自动创建作用域对象,我们只需将其注入即可。
1. 根作用域
$rootScope:AngularJS应用启动时会自动创建
2. AngularJS作用域继承
2.1 JavaScript对象继承机制
· JavaScript有两种方式构造对象:一是通过字面量创建
var obj = { name: "bob"};
· 二是通过构造方法,JS提供了几个内置的构造方法:Object、 Array、 String等
function Person(name) {
this.name = name;
}
var person = new Person("bob");
每个JS构造方法都有一个名称为prototype的属性,可以指向另一个对象。当我们访问对象属性时,JS引擎会从对象的所有属性中查找该属性,如果找到就返回属性值,如果没有找到就继续从prototype属性指向的对象属性中查找,如果仍然没有找到,则会沿着prototype链一直查找下去,直到prototype链结束或找到对象位置。
除了使用prototype实现继承外,还可以使用apply、call方法实现继承。由于JavaScript构造方法的apply()、call()方法可以改变对象构造中“this”的上下文环境,使特定的对象实例具有对象构造中所定义的属性、方法,因此我们可以使用apply()、call()方法实现JavaScript对象的继承。
function Person(name){
this.name = name;
}
function Student(name, love) {
//Person.apply(this,name);
Person.call(this,name);
this.love = love;
}
var student = new Student('bob','pingpong');
上例中,Student对象继承了Person对象的name属性。apply()和call()方法的不同之处在于apply()方法只接受两个参数,第二参数为数组,call()方法可以接收多个参数。
此外,JavaScript中,一个对象可以继承另一个对象的属性。
function Person(name) { this.name = name; }
var person = new Person('bob'); var student = Object.create(person); student.love = "pingpong";
Object.getPrototypeOf(student); // Person
总结一下,JavaScript中有三种对象继承方式:
· prototype Cat.prototype = new Animal();
· apply()/call()方法 在构造方法中调用这两个方法: Person.call(this, name);
· Object.create(person);
2.2 AngularJS作用域对象原型继承
AngularJS作用域对象继承为JavaScript的第一种方式。AngularJS作用域构造方法中提供了一个$new()成员方法,用于创建子作用域。
var parent = $rootScope;
var child = parent.$new();
AngularJS框架遍历DOM元素,查找到ng-app指令时,创建$rootScope作用域。然后AngularJS框架查找到第一个ng-controller指令,指向名称为OuterController的控制器,并调用$rootScope.$new()方法,以原型继承的方式创建$rootScope作用域的子作用域对象($scope1)。当OuterController构造方法接收一个名为$scope的参数时,AngularJS实例化控制器对象时会把$scope1对象注入控制器对象中。
接下来继续遍历,以同样的方式创建$scope1的子作用域,然后将其注入控制器对象中。
3 作用域高级特性
3.1. $watch方法监视作用域
angular.module("wacthModule",[])
.run(["$rootScope", function($rootScope) {
$rootScope.count = 0;
$rootScope.name = "bob";
$rootScope.$watch("name", function() {
$rootScope.count++;
})
}]);
使用作用域对象的$watch()方法对$rootScope中的name属性进行监视。在AngularJS内部,每当我们对ng-model绑定的name属性进行一次修改时,AngularJS内部的$digest循环就会运行一次,并在运行结束之后检查我们使用$watch()方法来监视的内容,如果和上一次进行$digest之前相比有了变化,就执行$watch()方法绑定的处理函数。
在AngularJS作用域对象的$watch()方法中,对基本类型和引用类型的操作有所不同。基本类型的监视与上面的例子一样,引用类型有所不同。
angular.module("wacthModule",[])
.run(["$rootScope", function($rootScope) {
$rootScope.count = 0;
$rootScope.items = [{ "value" : 1},{ "value" : 2}];
$rootScope.$watch("items", function() {
$rootScope.count++;
})
}]);
$watch()方法在对待基本类型和引用类型时会有不同的处理方式,这时需要介绍$watch()方法的第三个参数,第三个参数默认情况下为false。在默认情况下,即不显示指明第三个参数或者将其指明为false时,我们进行的监视叫作引用监视(reference watch),也就是说只要监视对象的引用没有发生变化,就不算它发生变化。在上述例子中,将一个新的数组newItems赋值给items,此时才会认为监视的属性发生了变化,进行回调方法的调用。相反,只要将第三个参数设置为true,此时进行的监视就叫作“全等监视”,只要属性发生变化,就会执行相应的回调方法。
补充:
$watchCollection() :针对数组(集合)进行监视,它的性能介于全等监视和引用监视之间,即它并不会对集合中的每一项的属性进行监视,但是可以对数组的项目增减作出反应。
3.2 解除作用域监视
需要关注$watch()方法的返回值,该方法调用完毕后返回另一个方法,这时只需调用返回的方法即可解除作用域监视。
var unbindWatcher = $rootScope.$watch("num", function(newValue, oldValue) {
if (newValue == 2){ unbindWatcher(); }
$rootScope.count++;
});
3.3 $apply方法和$digest循环
实际上AngularJS中的双向绑定也是通过类似于$watch方法的机制实现的。
$digest循环:周期性地运行一个函数来检查scope模型中的数据是否发生了变化。在该循环中,watchers会被触发,当一个watcher被触发时,AngularJS会检测scope模型,如果它发生了变化,那么关联到该watcher的回调方法就会被调用。那么,$digest循环是在什么时候以各种方式开始的呢?
在调用了$scope.$digest()后,$digest循环就开始了。假设你在一个ng-click指令对应的事件处理方法中更改了scope的一条数据,此时AngularJS会自动地通过调用$digest()来触发一轮$digest循环。当$digest循环开始后,它会触发每个watcher,由每一个watcher对属性进行检查,判断是否执行回调函数。
当AngularJS作用域中的模型数据发生变化时,AngularJS会自动触发$digest循环,从而达到自动更新视图的目的。但在有些情况下,模型数据修改后需要我们手动调用$apply()方法来触发$digest循环。例如使用JavaScript的setTimeOut()方法来更新一个模型数据,AngularJS框架就没有办法知道我们修改了什么,也就无法触发$digest循环。这个时候,只要将setTimeOut()方法中的代码移到一个匿名方法中,然后把该匿名方法作为$apply()方法的参数。
setTimeOut(function() {
$scope.$apply(function() {
$scope.message = "信息内容";
});
});
3.4 $timeout 与 $interval服务介绍
$timeout 与$interval服务分别是为解决JavaScript中的setTimeOut() 与 setInterval()不能自动触发watcher的问题的方案。使用这两个服务就能够达到JavaScript中的效果,同时还能自动触发$digest循环。
$timeout(function(){}, 3000);
4 作用域事件路由与广播
AngularJS作用域支持两种事件传播方式:
· 事件从子作用域路由到父作用域中
· 事件从父作用域广播到所有子作用域
相关的方法有$on()、$emit()、$broadcast()
4.1 $emit方法实现事件路由
该方法用于实现事件从子作用域路由到父作用域中,$emit()方法的第一个参数为事件名称,后面可以传入一个或多个参数,这些参数能够被传递到父作用域注册的事件监听器中,$emit()方法使用如下:
$scope.$emit("infoEvent", {name:"bob", age: 12});
消息发送出去后,我们可以在父作用域中调用AngularJS作用域对象的$on()方法,注册一个事件监听器监听子作用域路由的事件:
2. $broadcast方法实现事件广播
所有注册了实践监听器的子作用域就能接收到父作用域的广播事件。
$scope.$broadcast("infoEvent", {name:"bob"});
3. 作用域对象$on方法详解
$on方法用于注册一个事件监听器,该方法接收两个参数,第一个参数是要监听的事件名称,第二个参数是事件处理方法。
$scope.$on("infoEvent", function(event, data) {});
event 参数为事件对象,第二个参数data为调用$emit()或$broadcast()方法传递的数据。需要注意的是,event事件对象具有一些使用的属性和方法。
· event.name: 事件的名称
· event.targetScope: 事件源作用域对象
· event.currentScope: 当前作用域对象
· event.stopPropagation(): 停止事件的进一步传播。该方法只对向父作用域路由事件起作用,当在某个事件监听处理方法中调用事件对象的stopPropagation()方法后,事件不会再向上级父作用域路由。
· event.preventDefault(): 将defaultPrevented属性设置为true,直到事件监听器的实现者采取行动之前才会检查defaultPrevented的值。
· event.defaultPrevented: 如果调用了event.preventDefault()方法,那么该属性将被设置为true。