原文地址:http://livedocs.macromedia.com/flex/2/docs/00001847.html
===================================================================
这部分以简短概述ActionScript的OOP历史为开始,接着讨论ActionScript3.0的对象模型,以及它是如何让新的AVM2(ActionScript虚拟机2)比起以往FlashPlayer所包含的AVM1,在执行效率上有了明显的提升.
小标题
ActionScript中的OOP历史
ActionScript 3.0的类对象(class object)
特性对象(The traits object)
原型对象(The prototype object)
命名空间AS3(The AS3 namespace)
ActionScript中的OOP历史
因为ActionScript 3.0构建于先前版本的ActionScript之上,所以了解ActionScript对象模型的发展,可能会有所帮助.
ActionScript一开始是以一种简单的脚本机制来作为早期Flash的创造工具.渐渐地,程序员们开始用它来创建复杂的程序.为了响应程序员的一些需求,在每次新的发布中都对其语言性能有一定的增强,为的就是方便复杂程序的创建.
ActionScript 1.0
ActionScript 1.0是Flash Player6或更早的版本中所用的语言.在如此早期的开发过程中,ActionScript对象模型是基于基本数据类型概念上的.一个ActionScript对象就是一个混合了一组属性(property)的数据类型.在我们讨论对象模型时,属性这个术语,包括一个对象内的所有内容,如变量,函数.
虽然,第一代ActionScript不支持用class关键字来定义类,但仍然可以用一个特殊的对象来定义,这个对象叫做原型(prototype)对象.它把本来应该使用关键字class定义的抽象数据类型,定义在一个具体的对象内.就像在JAVA和C++这种基于class的语言里所做的那样,基于原型的ActionScript 1.0,使用一个存在的对象作为其它对象的模型(或原型).在基于class的语言中,一个类的作用相当于该类对象的模板,而在基于原型的语言中,对象的模板是另一个对象的原型.
在ActionScript 1.0中创建一个类,需要定义一个构造函数.而函数确是一个实际的对象,并不是一个抽象的概念.创建的构造函数相当于该类实例的原型所属的对象.下面代码创建了一个名为Shape的类,定义了一个属性visible,并初始为true:
// 基类
function Shape() {
}
// 创建一个名为visible的属性
Shape.prototype.visible = true;
构造函数定义了一个Shape类,可以用new操作符来实例化,如下:
myShape = new Shape();
构造函数Shape是一个对象,作为Shape类的实例的原型,也能作为Shape类的子类的原型.
创建一个Shape类的子类有两个过程.
首先,创建子类的构造函数,如下:
// 子类
function Circle(id, radius) {
this.id = id;
this.radius = radius;
}
其次,使用new操作符来声明Shape类是Circle类的原型.默认情况下,所有创建的类都使用Object类作为其原型,意思就是说,当前Circle.prototype包含了一个一般的对象(Object类的实例).为了让Shape成为Circle的原型,使用以下代码改变Circle.prototype,让其包含一个Shape对象.
// 让Circle成为Shape的子类
Circle.prototype = new Shape();
现在,Shape类和Circle类以一种继承关系,也就是所谓的原型链(prototype chain)连在了一起,下图描绘出了这种原型链关系:
每个原型链的基类都是Object类.Object类包含了一个静态属性,叫做Object.prototype,它被指定成为所有被创建对象的原型.在原型链中的第二个对象是Shape.因为Shape.prototype属性没有被明确设置,所以缺省保留的是一个一般对象(Object类的实例).原型链的末端是Circle类,它的原型被连接到了Shape类(Circlr.prototype持有一个Shape对象).
如果创建一个Circle类的实例,实例将继承自Circle类的原型链:
// 创建一个Circle类的实例
myCircle = new Circle();
重新调用先前创建的属性visible.在该例中,visible属性不是myCircle对象的一部分,而是Shape对象的一个成员,但仍然输出为true:
trace(myCircle.visible); // 输出: true
Flash Player为了确定myCircle继承了visible属性,于是走了一遍原型链.当执行trace代码时,Flash Player首先搜索整个myCircle的属性,找有没有一个叫visible的,可是没有找到.Flash Player继续在Circle.prototype中找,但仍没找到.不断沿着原型链向上,终于在Shape.prototype中找到了visible属性,于是输出该属性的值.
这个简单而有趣的例子,忽略了大多细节和复杂的原型链,目的是为了提供足够的信息帮助你了解ActionScript 3.0 对象模型.
ActionScript 2.0
ActionScript 2.0引进了新的关键字,class,extends,public和private.这些可以让类的定义近似于JAVA和C++这种语言.但重要的是要知道,ActionScrtip 2.0和ActionScrtip 1.0比起来,底层继承机制并没有任何改变.ActionScrtip 2.0仅仅是增加了一个新的语法来定义类罢了.工作方式还是一如既往的原型链.
下例中介绍了ActionScript 2.0的新语法,使得定义一个类,变的非常直观:
// 基类
class Shape {
var visible:Boolean = true;
}
注意ActionScript 2.0还引进了编译期类型检测.上述的声明,使得visible属性只能包含一个Boolean值.新的关键字extends,使创建子类的过程得到了简化.在ActionScript 1.0中需要两步的,只需要一步了:
// 子类
class Circle extends Shape {
var id:Number;
var radius:Number;
function Circle(id, radius) {
this.id = id;
this.radius = radius;
}
}
现在,构造函数成为类定义的一个部分,而属性id和radius必须被明确声明.
ActionScript 2.0还支持接口的定义.这点增强了面向对象编程,使内部对象通讯有了正式协议的定义,
ActionScript 3.0的类对象
一般的面相对象编程范例(paradigm) - 大多想到JAVA和C++ - 是使用各种类来定义对象的类型.
程序设计语言采用该范例,用类来构造实例的数据类型,这就是类的定义.
ActionScript使用的类也是为了这两个意图,但根本上,是作为一个基于原型的语言增加了这么一个特性.ActionScript为每个类的定义创建了一个特殊对象,来共享其行为和状态.大多数ActionScript程序员,是不会在实际编程中牵涉到这个特性.ActionScript 3.0在设计上,使你在以面相对象编程时,不会用到甚至也不用着了解这些特殊的类对象.这部分是为了给想利用这种特殊类对象的高级程序员作得深层次讨论.
下图显示了一个类对象的结构,由语句class A{};定义的一个简单的类A:
图中每一个矩形表示一个对象.对象内下脚的字母A,表示其属于类A.类对象(CA)包含了一系列其它重要对象的引用.特性对象(TA)(traits object)保存了定义在类中实例的属性.类特性对象(TCA)表示了类的内在类型,且以静态属性的方式保存在类中(下脚的字母C,表示"类").原型对象(PA)总是引用类对象一开始就有的constructor属性.
特性对象
在ActionScript 3.0中,特性对象作为新内容,目的是为了提升执行效率.在以往版本的ActionScript里,沿着原型链搜索变量名,是一个很耗时的过程.在ActionScript 3.0中,名称的搜索效率有着显著的提高,因为从父类继承下来的属性被复制到了子类的特性对象内.
程序员无法从代码中直接访问特性对象,但是能很明显的感觉到,它的存在改进了效率和内存的使用.特性对象为AVM2提供了关于类的设计和内容方面的详细信息.根据这些信息,AVM2可以大大减少执行时间,因为它常常能直接产生机器指令来访问属性或者调用函数,而无需耗时的去搞名称搜索.
感谢特性对象,让一个对象的内存占用远远小于了以往版本的ActionScript.举个例子来说,如果一个类是封闭的(即,类没有被定义成动态dynamic),该类的实例就不需要一个能动态添加属性的hash表,只需保存一个特性对象的引用和一些在类定义中的固定属性.结果,一个在ActionScript 2.0里占据内存100字节的对象,在ActionScript 3.0里面只占用了20字节.
注意: 特性对象是一个内在执行体,不保证在将来的ActionScript版本中,不作变动或者去除.
☆
原型(prototype)继承灵巧有余而效率乏力。在AS2/1中,prototype对象并非只读,它充许程序员动态设置prototype对象,这种机制灵活性巨大,但过度灵活方便的prototype却是运行时方法执行效率低下的罪魁祸首,AVM1不得不在变量、方法寻址上浪费大量的时间。
为了解决这个问题,在AS3中,引入了Traits对象,Traits对象在FP内部称为traits_info。每一个类拥有一个traits_info数组,每一个traits_info包括N个Trait对象,Trait是类或对象的固定属性,包括一个名字,一个类型,以及相关数据。
如果有一个Class A继承于Class B,那么A会把B的traits_info复制到自己的traits_info数组中(有一类traits除外,见下)。所有traits_info持有的Trait对象均是引用,所以过度的继承并不会显著增加内存的开销。
在上图中,对于Class A,Ta是其实例变量,实例方法的Traits对象,Tca是其类变量,类方法的Traits对象,它们同处于class_info(FP中Class的内部结构定义名称)中的traits_info数组中,但是Tca在继承时是不会被复制的,所以在子类中无法访问父类的静态变量、方法。
Tca与Ta同处于traits_info中,在名称寻址时,Ta的优先级高于Tca,这意味着,如果在Ca中存在着同名的类变量与实例变量,实例变量将被优先读取。
AS3使用Traits对象实现固定属性继承,相比原来的原型链机制,可在运行时显著减少名称寻址的时间开销。这是AVM2效率提升的最主要原因之一。
PS: 上图中,Class.prototype是类原型,Object.prototype是实例原型,后者是在对象实例化时分配的。
编码规范:尽量不要使用prototype。
原型对象
每一个ActionScript类对象都有一个属性叫prototype,它指向类的原型对象.原型对象是ActionScript作为基于原型语言遗留下来的.详细信息,查看ActionScript 1.0.
prototype属性是只读的,不能修改其指向其它对象.这点和以往的ActionScript不一样.虽然prototype属性是只读的,但它指向的原型对象不是.换句话说,可以为原型对象增加新的属性.增加的新属性可以被所有该类的实例共享.
原型链是以往的ActionScript中唯一的继承机制.在ActionScript 3.0中它只是一个次要角色.主要的继承机制,固定属性继承,已由特性对象在内部处理了.
固定属性就是指在类中定义一个变量或函数,定义的变量或函数则作为类定义的一个部分.
固定属性继承也叫做类继承,和该继承机制相关联的关键字有,如class,extends,override.
原型链提供的是另一种继承机制,比起固定属性继承更具动态性.你可以向一个类的原型对象中添加任何属性,该属性不一定是类定义的一部分,可以是在运行期内通过类对象的prototype属性添加的.注意,当设置编译器为严格模式时,是无法对添加在原型对象上的属性进行访问的,除非用dynamic声明该类是动态的.
一个很好的例子就是,Object类的原型对象上添加的几个属性.toString()和valueOf()函数,就是被分配在Object类的原型对象上的.下面这个例子,从理论上讲了如何声明这两个函数(执行起来因具体情况不同而有所差异)
public dynamic class Object {
prototype.toString = function () : String {
// 语句
}
prototype.valueOf = function () {
// 语句
}
}
正如之前提到的,还可以从外部向类的原型对象添加属性:
Object.prototype.toString = function () : String {
// 语句
}
在重新定义子类函数时,原型继承不像固定属性继承那样,需要override关键字.比如,如果要重新定义Object子类中的valueOf()函数,有三种方法.
其一,在子类的定义内,于其原型对象上定义一个valueOf()函数.以下代码创建了一个Object的子类叫Foo,且在Foo类的定义内,对其原型对象上的valueOf()函数进行了重新定义.因为每个类都继承自Object,所以extends可以省略.
dynamic class Foo{
prototype.valueOf = function() {
return "Instance of Foo";
}
}
其二,在外部重新定义,如下:
Foo.prototype.valueOf = function() {
return "Instance of Foo";
}
其三,在Foo类的定义内,定义一个名为valueOf()的固定属性.这个技术不同于其它两个,它混用了固定属性继承和原型继承.任何一个Foo的子类要想重定义valueOf(),都必须使用override关键字,以下代码显示了,在Foo中定义的一个valueOf()固定属性:
class Foo {
function valueOf() {
return "Instance of Foo";
}
}
命名空间AS3
在有着两种不同的继承机制下 - 固定属性继承和原型继承 - 要解决有关核心类的属性/函数的兼容性,是一个有趣的挑战.
一方面要兼容ECMAScript 4所要求的原型继承,即在一个核心类的原型对象上定义的属性/函数,另一方面,还要兼容Flash Player API所使用的固定属性继承,即在类定义中使用const,var,function关键字定义的属性/函数.此外,在使用上,要让固定属性比原型的执行效率有显著提升.
ActionScript 3.0通过为核心类使用原型继承和固定属性继承,解决了上述问题.每一个核心类包含了对属性/函数的两种设置.一种设置,就是兼容ECMAScript所要求的通过原型定义;另一种设置,是兼容Flash Player API的由固定属性和命名空间AS3来定义.
命名空间AS3在两种设置的选择上,提供了一个便利机制.如果不使用命名空间AS3,一个核心类实例的属性/函数,将继承自该核心类原型对象上所定义的内容.反之,如果使用命名空间AS3,则一个核心类实例的属性/函数,将来自AS3语言.因为固定属性总是优先于原型属性.换句话说,只要一个固定属性存在,使用时就将一直替代和该属性同名的原型属性.
你可以通过修饰符来使用命名空间AS3中的属性/函数.下面这个例子,介绍了如何使用AS3的Array.pop()函数:
var nums:Array = new Array(1, 2, 3);
nums.AS3::pop();
trace(nums); // 输出: 1,2
也可以利用use namespace在代码中直接打开命名空间AS3.下面这个例子,介绍了利用use namespace直接打开命名空间AS3来使用pop()和push()函数:
use namespace AS3;
var nums:Array = new Array(1, 2, 3);
nums.pop();
nums.push(5);
trace(nums) // 输出: 1,2,5
ActionScript 3.0的编辑器提供了一些选项,用来设置整个程序是否应用命名空间AS3.编译选项-as3指AS3命名空间,选项-es指原型继承(es为ECMAScript).要打开命名空间AS3,就把-as3设置为true,-es设置为false.反之亦然.Adobe Flex Builder 2的默认设置为 -as3=true -es=false
如果想扩展任何核心类,重载其任何函数.你应该了解了命名空间AS3所带来的影响,且如何去声明一个重载函数.如果是使用命名空间AS3,那么重载任何一个核心类的函数就必须使用命名空间AS3,并且要标有override.如果不是,则用不着使用命名空间AS3和override关键字.