- AngularJS入门与进阶
- 江荣波
- 2489字
- 2020-11-28 23:44:33
5.2 AngularJS作用域继承
5.2.1 JavaScript对象继承机制
学习AngularJS作用域继承之前,我们需要先了解一下JavaScript对象的继承机制。JavaScript语言遵循ECMAScript规范,在ECMAScript 6规范之前,JavaScript语言并没有类的概念,也不具备面向对象多态的特性,所以严格地讲JavaScript并不是一门面向对象语言,而是一门基于对象的语言。
JavaScript构造对象通常有两种方式。第一种方式是通过字面量创建,形式如下:
var obj = { name:'jane', age:32 };
另一种方式是通过对象的构造方法来创建,需要用到function关键字。JavaScript语言中的方法可以作为构造方法创建对象,比较容易让人产生疑惑。例如,我们使用下面这段代码定义一个构造方法:
function Person(name, age){ this.name = name; this.age = age; this.eat = function() { console.log('eat..'); } }
但是我们把它作为普通方法调用就不会有问题。当使用构造方法创建对象时需要用到JavaScript的new关键字,使用方法如下:
var person = new Person('Jane',32);
在实际项目中,当我们明确地使用function关键字定义一个构造方法时,构造方法名称通常采用帕斯卡命名法,即每个单词首字母大写(例如:LoginController);而使用function定义一个普通方法时,方法名称可采用驼峰命名法,驼峰命名法跟帕斯卡命名法相似,只是首字母为小写(例如:checkUser),看上去像驼峰,因此而得名。
JavaScript语言为我们提供了几个内置的构造方法,例如Object、Array 、String、Boolean等。我们可以直接使用这些构造方法创建对象。
了解了JavaScript对象的创建方法后,我们再来看看JavaScript的对象继承机制。JavaScript语言有以下3种方式实现对象继承。
1.构造方法原型链继承
每个JavaScript构造方法都有一个名称为prototype的属性,可以指向另一个对象。当我们访问对象属性时(例如obj.name), JavaScript引擎会从对象的所有属性中查找该属性,如果找到就返回属性值,如果没有找到就继续从prototype属性指向的对象属性中查找,如果仍然没有找到,则会沿着prototype链一直查找下去,直到prototype链结束或找到对象为止。接下来我们看一个原型链继承的案例,代码如下:
代码清单:ch05\ch05_02.html
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>ch05_02</title> </head> <body> <script type="text/javascript"> function Animal(){ this.eat = function(){ console.log('eat...'); } } function Cat(age){ this.age = age; } Cat.prototype = new Animal(); var cat = new Cat(10); console.log("cat.age=" + cat.age); cat.eat(); </script> </body> </html>
如上面的代码所示,首先定义两个构造方法Animal和Cat,把Cat的prototype属性指向一个Animal对象:
Cat.prototype = new Animal();
接下来通过new关键字创建一个Cat对象:
var cat = new Cat(10);
在浏览器中运行该案例,控制台输出:
cat.age=10 eat...
Cat构造方法中并没有定义eat()方法,而我们通过Cat实例调用eat()方法输出了内容,说明Cat对象通过prototype属性继承了Animal对象的eat()方法。
2.使用apply、call方法实现继承
由于JavaScript构造方法的apply()、call()方法可以改变对象构造中“this”的上下文环境,使特定的对象实例具有对象构造中所定义的属性、方法,因此我们可以使用apply()、call()方法实现JavaScript对象的继承,例如下面的案例:
代码清单:ch05\ch05_03.html
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>ch05_03</title> </head> <body> <script type="text/javascript"> function Person(name, age){ this.name = name; this.age = age; } function Student(name, age, love){ //Person.apply(this, [name, age]); Person.call(this, name, age); this.love = love; } var student = new Student('jane',23, 'pingpong'); console.log("student.name=" + student.name); console.log("student.age=" + student.age); console.log("student.love=" + student.love); </script> </body> </html>
如上面的代码所示,在本例中我们首先定义了Person构造方法,接着在Student构造方法中调用Person.call()方法实现了继承,然后通过new关键字创建一个Student对象,在控制台中输出Student对象的属性值。在浏览器中预览ch05_03.html页面,打开开发人员工具,控制台输出内容如下:
student.name=jane student.age=23 student.love=pingpong
说明Student对象继承了Person对象的name和age属性。apply()方法和call()方法的不同之处在于apply()方法只接收两个参数,第二个参数是一个数组,而call()方法可以接收多个参数。
3.对象实例间继承
在JavaScript语言中,对象可以继承另外一个对象的属性,例如下面的案例:
代码清单:ch05\ch05_04.html
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>ch05_04</title> </head> <body> <script type="text/javascript"> function Person(name, age){ this.name = name; this.age = age; } var person = new Person('jane',28); var student = Object.create(person); student.love = "pingpong"; console.log(Object.getPrototypeOf(student)); console.log("student.name→"+student.name); console.log("student.age→"+student.age); console.log("student.love→"+student.love); </script> </body> </html>
如上面的代码所示,在本例中我们用到了Object.create()方法,它的作用是以一个对象为原型创建另外一个对象,创建的对象和原对象具有相同的属性,我们可以通过Object. getPrototypeOf()方法获取新对象的原型。
在浏览器中运行ch05_04.html页面,打开开发人员工具,控制台输出内容如下:
Person student.name→jane student.age→28 student.love→pingpong
结合源代码,从输出的日志信息可以看出,student对象继承了Person对象的name和age属性,调用Object.getPrototypeOf()方法获取student对象的原型依然为Person。
本节中笔者对JavaScript对象的继承方式进行了比较全面的学习,下一小节我们一起学习AngularJS如何使用原型方式实现作用域对象的继承。
5.2.2 AngularJS作用域对象原型继承
上一小节介绍了JavaScript语言中对象继承的3种方式,其中AngularJS作用域对象继承采用第一种方式,即构造方法原型链继承。AngularJS作用域构造方法中提供了一个$new()成员方法,用于创建子作用域。AngularJS框架创建子作用域的过程大致如下:
var parent = $rootScope; var child = parent.$new();
我们不妨了解一下$new()方法的定义,内容如下,有兴趣的读者可以参考AngularJS源码。
Scope.prototype = {
constructor: Scope,
$new: function(isolate, parent){
var child;
parent = parent || this;
if(isolate){
child = new Scope();
child.$root = this.$root;
} else {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if(! this.$$ChildScope){
this.$$ChildScope = createChildScopeClass(this);
}
child = new this.$$ChildScope();
}
child.$parent = parent;
child.$$prevSibling = parent.$$childTail;
if(parent.$$childHead){
parent.$$childTail.$$nextSibling = child;
parent.$$childTail = child;
} else {
parent.$$childHead = parent.$$childTail = child;
}
// When the new scope is not isolated or we inherit from `this`, and
// the parent scope is destroyed, the property `$$destroyed` is
// inherited prototypically. In all other cases, this property
// needs to be set when the parent scope is destroyed.
// The listener needs to be added after the parent is set
if(isolate || parent ! = this)child.$on('$destroy',
destroyChildScope);
return child;
},
......
}
上面的代码为AngularJS1.5.5版本中$new()方法的定义,如上面黑体代码所示。其中,this.$$ChildScope为子作用域构造方法,由createChildScopeClass()方法调用返回。下面是createChildScopeClass()方法的定义:
function createChildScopeClass(parent){ function ChildScope() { this.$$watchers = this.$$nextSibling = this.$$childHead = this.$$childTail = null; this.$$listeners = {}; this.$$listenerCount = {}; this.$$watchersCount = 0; this.$id = nextUid(); this.$$ChildScope = null; } ChildScope.prototype = parent; return ChildScope; }
createChildScopeClass()方法中定义了ChildScope构造方法,ChildScope即为子作用域的构造方法,接着指定ChildScope的prototype属性为parent, parent即为父作用域对象,这样子作用域就继承了父作用域的所有属性。
上面我们了解了AngularJS作用域继承机制,所有作用域对象都是$rootScope作用域的子作用域。还有一种情况需要我们考虑,即在AngularJS控制器中可以嵌套另外一个控制器,例如:
<div ng-app> <div ng-controller="OuterController"> <div ng-controller="InnerController"> </div> </div> </div>
在上面的代码片段中,ng-controller指令范围内嵌套了另外一个ng-controller指令,这种情况下AngularJS是如何处理作用域对象继承的呢?过程如下:
AngularJS框架遍历DOM元素,查找到ng-app指令时启动应用,创建$rootScope作用域。
然后AngularJS框架查找到第一个ng-controller指令,指向名称为OuterController的控制器,并调用$rootScope.$new()方法,以原型继承的方式创建$rootScope作用域的子作用域对象(记为$scope1)。当OuterController构造方法接收一个名称为$scope的参数时,AngularJS实例化控制器对象时会把$scope1对象注入控制器对象中。
接下来AngularJS继续遍历DOM元素,遇到第二个嵌套的ng-controller指令时调用$scope1. new()方法,以$scope1为原型创建子作用域(记为$scope2, $scope2作用域对象能够访问$scope1作用域对象的所有属性)。
除了ng-app、ng-controller指令会创建作用域对象外,AngularJS指令也可能会产生子作用域,在后面的章节中我们会接触到。本节内容就介绍这么多,下节我们开始学习AngularJS作用域监视机制。