继承 继承是指一种代码重用的形式,允许程序员基于现有类开发新类。现有类通常称为“基类”或“超类”,新类通常称为“子类”。继承的主要优势是,允许重复使用基类中的代码,但不修改现有代码。此外,继承不要求改变其它类与基类交互的方式。不必修改可能已经过彻底测试或可能已被使用的现有类,使用继承可将该类视为一个集成模块,可使用其它属性或方法对它进行扩展。因此,您使用 通过继承还可以在代码中利用“多态”。有一种方法在应用于不同数据类型时会有不同行为,多态就是对这样的方法应用一个方法名的能力。名为 Shape 的基类就是一个简单的示例,该类有名为 Circle 和 Square 的两个子类。Shape 类定义了名为 class Shape { public function area():Number { return NaN; } } class Circle extends Shape { private var radius:Number = 1; override public function area():Number { return (Math.PI * (radius * radius)); } } class Square extends Shape { private var side:Number = 1; override public function area():Number { return (side * side); } } var cir:Circle = new Circle(); trace(cir.area()); // 输出:3.141592653589793 var sq:Square = new Square(); trace(sq.area()); // 输出:1 因为每个类定义一个数据类型,所以使用继承会在基类和扩展基类的类之间创建一个特殊关系。子类保证拥有其基类的所有属性,这意味着子类的实例总是可以替换基类的实例。例如,如果方法定义了 Shape 类型的参数 (parameter),由于 Circle 扩展了 Shape,因此 Circle 类型的参数 (argument) 是合法的,如下所示: function draw(shapeToDraw:Shape) {} var myCircle:Circle = new Circle(); draw(myCircle); 子主题实例属性和继承 覆盖方法 不继承静态属性 静态属性和作用域链实例属性和继承对于实例属性 (property),无论是使用 对于某些类型的事件,Event 类包含了定义事件所需的所有属性。这些类型的事件不需要 Event 类中定义的实例属性以外的实例属性。 下面的示例是从 Event 类中摘录的,显示由子类继承的某些属性和方法。由于继承了属性,因此任何子类的实例都可以访问这些属性。 public class Event { public function get type():String; public function get bubbles():Boolean; ... public function stopPropagation():void {} public function stopImmediatePropagation():void {} public function preventDefault():void {} public function isDefaultPrevented():Boolean {} ... } 其它类型的事件需要 Event 类中没有提供的特有属性。这些事件是使用 Event 类的子类定义的,所以可向 Event 类中定义的属性添加新属性。MouseEvent 类就是这样的一个子类,它可添加与鼠标移动或鼠标单击相关的事件的特有属性,如 public class MouseEvent extends Event { public static const CLICK:String = "click"; public static const MOUSE_MOVE:String = "mouseMove"; ... public function get stageX():Number {} public function get stageY():Number {} ... } 访问控制说明符和继承如果某一属性是用 如果属性是使用
要限制某一属性在定义该属性的包中的可见性,请使用 可以使用下面的示例来查看每一个访问控制说明符如何影响跨越包边界的继承。以下代码定义了一个名为 AccessControl 的主应用程序类和两个名为 Base 的 Extender 的其它类。Base 类在名为 foo 的包中,Extender 类是 Base 类的子类,它在名为 bar 的包中。AccessControl 类只导入 Extender 类,并创建一个 Extender 实例,该实例试图访问在 Base 类中定义的名为 // Base.as 位于名为 foo 的文件夹中 package foo { public class Base { public var str:String = "hello"; // 在该行上更改 public } } // Extender.as 位于名为 bar 的文件夹中 package bar { import foo.Base; public class Extender extends Base { public function getString():String { return str; } } } // 名为 ProtectedExample.as 文件中的主应用程序类 import flash.display.MovieClip; import bar.Extender; public class AccessControl extends MovieClip { public function AccessControl() { var myExt:Extender = new Extender(); trace(myExt.testString); // 如果 str 不是 public,则发生错误 trace(myExt.getString()); // 如果 str 是 private 或 internal,则发生错误 } } } 要查看其它访问控制说明符如何影响先前示例的编译和执行,请在 trace(myExt.testString); // 如果 str 不是 public,则发生错误 不允许覆盖变量将继承使用 覆盖方法覆盖方法表示重新定义已继承方法的行为。静态方法不能继承,也不能覆盖。但是,实例方法可由子类继承,也可覆盖,只要符合以下两个条件:
要覆盖符合这些条件的实例方法,子类中的方法定义必须使用
但是,覆盖方法中的参数名不必与基类中的参数名相匹配,只要参数数和每个参数的数据类相匹配即可。 super 语句覆盖方法时,程序员经常希望在要覆盖的超类方法的行为上添加行为,而不是完全替换该行为。这需要通过某种机制来允许子类中的方法调用它本身的超类版本。 package { import flash.display.MovieClip; public class SuperExample extends MovieClip { public function SuperExample() { var myExt:Extender = new Extender() trace(myExt.thanks()); // 输出:Mahalo nui loa } } } class Base { public function thanks():String { return "Mahalo"; } } class Extender extends Base { override public function thanks():String { return super.thanks() + " nui loa"; } } 覆盖 getter 和 setter虽然不能覆盖超类中定义的变量,但是可以覆盖 getter 和 setter。例如,以下代码用于覆盖在 Flash Player API 的 MovieClip 类中定义的名为 package { import flash.display.MovieClip; public class OverrideExample extends MovieClip { public function OverrideExample() { trace(currentLabel) } override public function get currentLabel():String { var str:String = "Override: "; str += super.currentLabel; return str; } } } OverrideExample 类构造函数中的 不继承静态属性静态属性不由子类继承。这意味着不能通过子类的实例访问静态属性。只能通过定义静态属性的类对象来访问静态属性。例如,以下代码定义了名为 Base 的基类和扩展名为 Extender 的 Base 子类。Base 类中定义了名为 package { import flash.display.MovieClip; public class StaticExample extends MovieClip { public function StaticExample() { var myExt:Extender = new Extender(); trace(myExt.test); // 错误 } } } class Base { public static var test:String = "static"; } class Extender extends Base { } 访问静态变量 Base.test; 但允许使用与静态属性相同的名称定义实例属性。可以在与静态属性相同的类中或在子类中定义这样的实例属性。例如,以上示例中的 Base 类可以具有一个名为 package { import flash.display.MovieClip; public class StaticExample extends MovieClip { public function StaticExample() { var myExt:Extender = new Extender(); trace(myExt.test); // 输出:实例 } } } class Base { public static var test:String = "static"; public var test:String = "instance"; } class Extender extends Base {} 静态属性和作用域链虽然并不继承静态属性,但是静态属性在定义它们的类或该类的任何子类的作用域链中。同样,可以认为静态属性在定义它们的类和任何子类的“作用域”中。这意味着在定义静态属性的类体及该类的任何子类中可直接访问静态属性。 下面的示例对上一个示例中定义的类进行了修改,以说明 Base 类中定义的 package { import flash.display.MovieClip; public class StaticExample extends MovieClip { public function StaticExample() { var myExt:Extender = new Extender(); } } } class Base { public static var test:String = "static"; } class Extender extends Base { public function Extender() { trace(test); // 输出:静态 } } 如果使用与同类或超类中的静态属性相同的名称定义实例属性,则实例属性在作用域链中的优先级比较高。因此认为实例属性“遮蔽”了静态属性,这意味着会使用实例属性的值,而不使用静态属性的值。例如,以下代码显示如果 Extender 类定义名为 package { import flash.display.MovieClip; public class StaticExample extends MovieClip { public function StaticExample() { var myExt:Extender = new Extender(); } } } class Base { public static var test:String = "static"; } class Extender extends Base { public var test:String = "instance"; public function Extender() { trace(test); // 输出:实例 } }
处理日期和时间时间可能并不代表一切,但它在软件应用程序中通常是一个关键要素。ActionScript 3.0 提供了多种强大的手段来管理日历日期、时间和时间间隔。以下两个主类提供了大部分的计时功能:Date 类和 flash.utils 包中的新 Timer 类。 目录日期和时间基础知识 管理日历日期和时间 控制时间间隔 示例:简单的模拟时钟日期和时间基础知识处理日期和时间简介日期和时间是在 ActionScript 程序中使用的一种常见信息类型。例如,您可能需要了解当前星期值,或测量用户在特定屏幕上花费多少时间,并且还可能会执行很多其它操作。在 ActionScript 中,可以使用 Date 类来表示某一时刻,其中包含日期和时间信息。Date 实例中包含各个日期和时间单位的值,其中包括年、月、日、星期、小时、分钟、秒、毫秒以及时区。对于更高级的用法,ActionScript 还包括 Timer 类,您可以使用该类在一定延迟后执行动作,或按重复间隔执行动作。 常见日期和时间任务本章介绍了以下常见的日期和时间信息处理任务:
重要概念和术语以下参考列表包含将会在本章中遇到的重要术语:
完成本章中的示例学习本章的过程中,您可能想要自己动手测试一些示例代码清单。由于本章中的代码清单主要用于处理 Date 对象,测试示例将涉及查看示例中使用的变量值,方法是:通过将值写入舞台上的文本字段实例,或使用 管理日历日期和时间ActionScript 3.0 的所有日历日期和时间管理函数都集中在顶级 Date 类中。Date 类包含一些方法和属性,这些方法和属性能够使您按照通用协调时间 (UTC) 或特定于时区的本地时间来处理日期和时间。UTC 是一种标准时间定义,它实质上与格林尼治标准时间 (GMT) 相同。 子主题创建 Date 对象 获取时间单位值 执行日期和时间运算 在时区之间进行转换创建 Date 对象Date 类是所有核心类中构造函数方法形式最为多变的类之一。您可以用以下四种方式来调用 Date 类。 第一,如果未给定参数,则 var now:Date = new Date(); 第二,如果仅给定了一个数字参数,则 var millisecondsPerDay:int = 1000 * 60 * 60 * 24; // 获取一个表示自起始日期 1970 年 1 月 1 日后又过了一天时间的 Date 对象 var startTime:Date = new Date(millisecondsPerDay); 第三,您可以将多个数值参数传递给 var millenium:Date = new Date(2000, 0, 1, 0, 0, 0, 0); 第四,您可以将单个字符串参数传递给 var nextDay:Date = new Date(“Mon May 1 2006 11:30:00 AM”); 如果 获取时间单位值可以使用 Date 类的属性或方法从 Date 对象中提取各种时间单位的值。下面的每个属性为您提供了 Date 对象中的一个时间单位的值:
实际上,Date 类为您提供了获取这些值的多种方式。例如,您可以用四种不同方式获取 Date 对象的月份值:
所有四种方式实质上具有同等的效率,因此您可以任意使用一种最适合应用程序的方法。 刚才列出的属性表示总日期值的各个部分。例如,milliseconds 属性永远不会大于 999,因为当它达到 1000 时,秒钟值就会增加 1 并且 milliseconds 属性会重置为 0。 如果要获得 Date 对象自 1970 年 1 月 1 日 (UTC) 起所经过毫秒数的值,您可以使用 执行日期和时间运算您可以使用 Date 类对日期和时间执行加法和减法运算。日期值在内部以毫秒的形式保存,因此您应将其它值转换成毫秒,然后再将它们与 Date 对象进行加减。 如果应用程序将执行大量的日期和时间运算,您可能会发现创建常量来保存常见时间单位值(以毫秒的形式)非常有用,如下所示: public static const millisecondsPerMinute:int = 1000 * 60; public static const millisecondsPerHour:int = 1000 * 60 * 60; public static const millisecondsPerDay:int = 1000 * 60 * 60 * 24; 现在,可以方便地使用标准时间单位来执行日期运算。下列代码使用 var oneHourFromNow:Date = new Date(); oneHourFromNow.setTime(oneHourFromNow.getTime() + millisecondsPerHour); 设置日期值的另一种方式是仅使用一个毫秒参数创建新的 Date 对象。例如,下列代码将一个日期加上 30 天以计算另一个日期: // 将发票日期设置为今天的日期 var invoiceDate:Date = new Date(); // 加上 30 天以获得到期日期 var dueDate:Date = new Date(invoiceDate.getTime() + (30 * millisecondsPerDay)); 接着,将 在时区之间进行转换在需要将日期从一种时区转换成另一种时区时,使用日期和时间运算十分方便。也可以使用 以下示例使用时区偏移量将日期从本地时间转换成 UTC。该示例首先以毫秒为单位计算时区值,然后按照该量调整 Date 值: // 按本地时间创建 Date var nextDay:Date = new Date("Mon May 1 2006 11:30:00 AM"); // 通过加上或减去时区偏移量,将 Date 转换为 UTC var offsetMilliseconds:Number = nextDay.getTimezoneOffset() * 60 * 1000; nextDay.setTime(nextDay.getTime() + offsetMilliseconds);
这些函数仍保留在 ActionScript 3.0 以实现向后兼容。Adobe 不建议您在新的 ActionScript 3.0 应用程序中使用这些函数。通常,在应用程序中使用 Timer 类会更容易且更有效。 示例:简单的模拟时钟简单的模拟时钟示例说明了在本章中讨论的以下两个日期和时间概念:
要获取该范例的应用程序文件,请访问 www.adobe.com/go/learn_programmingAS3samples_flash_cn。可以在 Samples/SimpleClock 文件夹中找到 SimpleClock 应用程序文件。该应用程序包含以下文件:
子主题定义 SimpleClock 类 创建钟面 启动计时器 显示当前时间定义 SimpleClock 类此时钟示例很简单,但是将即使很简单的应用程序也组织得十分有条理是一种很好的做法,以便您将来能够很轻松地扩展这些应用程序。为此,SimpleClock 应用程序使用 SimpleClock 类处理启动和时间保持任务,然后使用另一个名称为 AnalogClockFace 的类来实际显示该时间。 以下代码用于定义和初始化 SimpleClock 类(请注意,在 Flash 版本中,SimpleClock 扩展了 Sprite 类): public class SimpleClock extends UIComponent { /** * 时间显示组件。 */ private var face:AnalogClockFace; /** * Timer 的作用就像是应用程序的心跳。 */ private var ticker:Timer; 该类具有两个重要的属性:
SimpleClock 类使用默认构造函数。 创建钟面SimpleClock 代码中后面的几行代码创建用于显示时间的钟面: /** * 设置 SimpleClock 实例。 */ public function initClock(faceSize:Number = 200) { // 创建钟面并将其添加到显示列表中 face = new AnalogClockFace(Math.max(20, faceSize)); face.init(); addChild(face); // 绘制初始时钟显示 face.draw(); 可以将钟面的大小传递给 接着,应用程序将钟面初始化,然后使用从 DisplayObject 类继承的 启动计时器创建钟面后, // 创建用来每秒触发一次事件的 Timer ticker = new Timer(1000); // 指定 onTick() 方法来处理 Timer 事件 ticker.addEventListener(TimerEvent.TIMER, onTick); // 启动时钟计时 ticker.start(); 首先,该方法初始化一个每秒(每隔 1000 毫秒)调度一次事件的 Timer 实例。由于没有向
public function onTick(event:TimerEvent):void { // 更新时钟显示 face.draw(); }
显示当前时间AnalogClockFace 类中大多数代码都与设置钟面的显示元素有关。在对 AnalogClockFace 进行初始化时,它会绘制一个圆形轮廓,将数字文本标签放在每个小时刻度处,然后创建三个 Shape 对象,分别表示时钟的时针、分针和秒针。 在 SimpleClock 应用程序运行后,它会每秒调用一次 /** * 在绘制时钟显示时由父容器进行调用。 */ public override function draw():void { // 将当前日期和时间存储在实例变量中 currentTime = new Date(); showTime(currentTime); } 此方法将当前时间保存在变量中,因此在绘制时钟指针的过程中无法改变时间。然后,调用 /** * 以看起来不错的老式模拟时钟样式显示指定的 Date/Time。 */ public function showTime(time:Date):void { // 获取时间值 var seconds:uint = time.getSeconds(); var minutes:uint = time.getMinutes(); var hours:uint = time.getHours(); // 乘以 6 得到度数 this.secondHand.rotation = 180 + (seconds * 6); this.minuteHand.rotation = 180 + (minutes * 6); // 乘以 30 得到基本度数,然后 // 最多加上 29.5 度 (59 * 0.5) // 以计算分钟。 this.hourHand.rotation = 180 + (hours * 30) + (minutes * 0.5); } 首先,此方法提取当前时间的小时、分钟和秒的值。然后使用这些值来计算每个指针的角度。由于秒针会在 60 秒内旋转一圈,因此它每秒都会旋转 6 度 (360/60)。分针每分钟都旋转同样的度数。 时针每分钟都在更新,因此时针能够随着分针的跳动显示出某些时间变化过程。时针每小时旋转 30 度 (360/12),但也会每分钟旋转半度(30 度除以 60 分钟)。 |