AS3日积月累(1) – AS3的面向对象特点概述

原文章地址:http://as3blog.com/as3/as3tip-oop/


本文是我(aw)在整理了相关文档和讨论之后,结合自己的亲自实验总结出来的一些经验和心得。我尽量描述详尽,避免模糊概念,当然也希望所有看官提出批评意见。为了表述方便,其中术语不限定语言,如我可能会一会儿用class,一会儿用“类”。


面向对象的难点部分就是理解变量作用域修饰符(modifier)其实也就是面向对象中我们已经熟悉的public、protected、private等等。本文还深入讨论了ActionScript3中新增的internal等概念。下面我依次列出:


一、关于package以及internal
package,用“形而上学”的方式理解,就是物理目录下的类集合。在AS2中只需要保证文件系统的路径匹配,然后用类似“import com.awflasher.someUtils”的方法导入即可。而AS3则要求您在所有的类中声明package关键词。package的大括号对“{}”内,我们只能定义一个类,我们可以在这个大括号外面定义一些辅助类,不过这些类只能被当前这个类(你在package大括号对内定义的类)访问。当然,一个package大括号对内只有一个类,这并不代表一个package内只有一个类。你可以在同一目录下定义多个属于该package(指代这个目录)的类。它的意义绝不是简单的“类文件集合容器”,而是一个让各种应该协同工作的类集中到一起的项目包。值得一提的是,所谓“协同工作”是指至少有一个class要引入其他一些class来进行功能设计,而这时候采用internal修饰可以省去很多getters和setters。我自己回忆起在湖南卫视的项目中用AS2开发的Vplayer,两个类AVCore和AVControl就有很多getter和setter,搞的特别麻烦。internal类似public,但限定在一个package内了。在同一个package内的类可以访问同一个package内其他类的internal变量,而其他包内的类无法访问。package与类的继承性毫无关系,比如TextField和Sprite、MovieClip都继承自DisplayObject类,但TextField属于flash.text包,而MovieClip和Sprite属于flahs.display包。也就是说,包对类的限定是与继承链毫无关联的、一个新的“维度”的限定。
附:使用一个类的时候,我们必须import这个类,或者包含这个类的package。AS2时直接写完整包路径的使用方法在AS3中不管用了,本文后面有详细介绍。


二、关于public
public定义的类或者属性可以在任何作用域内由任何来源访问。构造函数永远都是public的,Flex中的应用程序类(Application Class)和Flash CS3中的文档类(Document Class)必须是public的。且不能缺省public这个关键词声明。我在测试中发现,如果不声明public,Flash根本就不会获取类的定义,进而编译无法通过。
非常有必要啰嗦一下:public可以穿越package,而类又不能使用namespace(参阅FlashCS3帮助文档:Applying namespaces)。因此,所有被文档类调用的其它包中的类,应该一致声明为public的 。因为文档类在一个独立的包中。


三、关于protected
protected声明类似AS2的private,它定义的属性只能在自己子类中可见,而其它场合都是不可见的。这一点与Java等传统OOP语言类似。


四、关于private
注意AS3的private和AS2的private大不相同,它定义的属性只属于自己,子类可以定义毫无牵连的同名属性。
dynamic 和原来AS2的dynamic一样,用dynamic声明的类可以动态的加入属性。这些属性也可以通过delete来删除。动态加入的属性一旦被切断所有的引用就会被垃圾回收机制自动回收。有时候用System.totalMemory检测不到内存释放是因为垃圾回收机制并不是即时运行的。


五、关于dynamic
动态(dynamic)类允许在运行时动态地添加属性,常见的动态类有MovieClip和顶级(top-level)的Array。如果您的自定义类要继承于动态类,那么请也定义为动态的,不要省略dynamic关键词。如果您喜欢阅读英文教程,会看到很多“sealed class”,其含义即“非dynamic class”。只不过并没有sealed这个关键词(AS3中,类默认就是sealed的)。注意,在AS2中,“骨子里”是没有sealed class的!在run-time时,所有的AS2代码都变成了AS1的语法,sealed class无从说起了。可以说这是AS3的一个新产物。更多相关内容可以参见:http://as3blog.com/as3/as3tip-take-care-of-resource


六、关于继承(extends)和override
继承其实并不太复杂,唯一要说明的就是:子类的构造函数一定要用“super”调用一次父类的构造函数,否则报错!对于继承后的子类,如果要重新定义父类的非private方法,必须使用override关键词。在override的时候,如果我们需要调用父类的方法,可以使用super关键词(由于继承方法在逻辑上与父类往往有相似性,因此没有必要把方法逻辑完全重写)官方帮助中的这个例子非常易懂:
package {
import flash.display.MovieClip;
public class SuperExample extends MovieClip
{
public function SuperExample()
{
var myExt:Extender = new Extender()
trace(myExt.thanks()); // output: 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”;
}
}


override不能用于重载变量(成员属性)。但是却可以用于重写getter和setter函数,例如:(官方帮助的例子)
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;
}
}
}
这个例子中,我们直接重写了MovieClip类的currentLabel属性。注意调用父类属性的时候,用了super.currentLabel。
关于静态方法,比较麻烦。首先,静态方法是无法被重载的。必须通过类来访问。但是您也可以自己定义与静态方法同名的方法,我把官方的例子做了一下修改就一目了然了:
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
{
private var test:String = “instance”;
public function Extender()
{
trace(Base.test); // output: static
trace(test); //added by awflasher.com, output: instance
}
}
七、关于import语法
在AS2时代,“import”语法只是为了让编程时代码简洁(省去了包名),比如我们import了mx.transitions.Tween之后,就可以直接new Tween()了。而如果不import,我们也可以直接用全类名来构造实例,例如:
new mx.transitions.Tween();
然而,在AS3中,无论是否采用全名类声明,只要你用到这个类,就必须import。
import估计是传统Flash程序员所需要养成的一个最大的习惯,在刚切入AS3开发平台的时候,我常常忘记import一些常用的类,例如从IDE指向过来的文本和渲染元件,以及flash.event.*、flash.display.StageAlign等比较泛用的类。
AS3中不像AS2那样,我们不能用_root和Stage[”displayState”]来进行hacks了。(参见上面“五-关于 dynamic”)


八、关于编译时的注意事项
AS3不再像AS2那样简单地Complie-Time(编译时,即FlashCS3/FlexBuidler/其他编译器发布ActionScript及所有资源为SWF文件的那一时刻)进行类型检测了,AS3在Run-Time(运行时,级Flashplayer或者其他播放SWF的软件在播放SWF的时候)也有类型检测,因此AS2的Hacks(强制访问不能访问的属性)将不再有效。


九、一个不得不说的好消息
AS3中类所有的变量(属性)和函数(方法)的作用域都是运行时跟类/实例走的。这与AS2大有不同,我们不再需要去Delegate了。只要是类自己的方法,在调用的过程中,this永远指向类自己(实例)。


简要总结:
1、如果我需要属性公开,并且可以被自己的子类继承,那么定义成public的。子类也可重写(override)之。
2、如果我需要属性隐藏,但是可以被自己的子类继承,那么定义成protected的。与public类似,子类也可重写(override)之。
3、如果我的某一个类有一个属性不想在任何位置可见,包括其子类,那么定义为private的。其子类无需重写(override),因为它根本就不存在于子类中。


AS3日积月累(2) – 从AS1和AS2到AS3的观念转变


原文章地址:http://as3blog.com/as3/as3tip-new-philosophy/


AS1/2-AS3观念的转变(Meet with new philosophy)
对于AS1、AS2的开发模式来说,灵活是最大的优势。然而,灵活却造成了不稳定、紊乱。这是开发复杂的、长久的项目所忌讳的。关于(AS1/2/1+2)灵活轻便与稳定持久(AS3)的权衡,我个人觉得可以理解为”鱼和熊掌不可兼得”,但我希望已经习惯了AS1、AS2的朋友们不要把这个结论想得太悲观。


AS3是纯粹面向对象的,相比过去的AS2,我认为是更加敏捷的。纵然有着更多的约束,但在package内直接建立多个辅助类(Helper Class),不失为一个非常好的消息。就凭这一点,我觉得至少与笨拙的AS2相比,AS3的开发效率就不会打多大折扣。我们需要的其实只是语法、习惯,尤其是观念的转变而已。当然,这需要时间。我作为一个AS1/2的长期发开人员,在转变到AS3的过程之中,也遇到了很多问题和疑惑。但我很乐于与大家分享、交流我所获得的收获以及观念转变的心路历程。


ActionScript编程自它问世的那一天就是多姿多彩的。技术,尤其是Adobe产品线的技术体系,也绝然不是呆板的”学究式体系”。我希望我的”罗嗦”能让您获得一个更轻松的心态。


言归正传,先说说我在AS1/2(1+2)转变到AS3时所遭遇的最大困惑吧:
开局(How, and especially where, to get start) – 玩过星际争霸的朋友们一定知道,针对不同的地图,如Lost Temple和WCG-groky park(原来WCG有一个岛关,我忘记了),都有各自的经典、流行的开局方式。从AS1/2转变到AS3,无非是从Lost Temple转变到WCG-groky park的过程,你也许要先采气矿,造空军,才能顺利发展。


其实Flash从AS1到AS3,也有各自固定的、流行的开局方式。
对于习惯了用AS1编程的人来说,制作一个Flash的开局是非常灵活的:你一进入Flash就有一个长长时间轴以供使用。你往往需要一个loading,你可以用1-5帧先做一个loading(还记得N年前流行的FlashMTV制作教程么?);你也可以取一帧,放一个loading的MovieClip然后在这个MovieClip上写一个onEnterFrame来监听swf文件加载的进度(我热衷的做法)。接下来,你可以在第二帧或者第N帧部署程序界面。MovieClip强大的帧API能让你灵活地完成许多有趣的逻辑(gotoAndPlay、gotoAndStop、prevFrame等)。编程的时候也可以很随意地寻找自己要控制的资源,我现在还记得刚接触AS的时候,一个_root一个_global,曾经让我屡试不爽。每次遇到问题了就用这两个东西解决。
AS2的开局其实没有本质的变化,至少我是这么认为的。唯一的进步就是比AS1的OOP,模块封装的更加彻底。甚至还有些许退步,比如清一色基于MovieClip+attachMovie的模式,仍然容易造成运行时(Run-Time)效率低下,而且开发起来概念也模糊了。因为Library中设置了linkage,new的明明是自己的Class,attach的还是MovieClip。
于是很多人采用AS1+2的方式,这也是我所喜欢的。现在想起来,还是比较灵活快速的。
然而在AS3中,你却仿佛陷入一片黑暗。FlexBuilder没有时间轴。即便用”似曾相识”的FlashCS3的IDE开发,AS3也不支持MovieClip和Button上的代码。写在帧上也无法简单地使用”onRelease=functioin”了。上网搜一搜教程,往往得到如下的写法:
aw.addEventListener(”click”,fun);
function fun(e:Event){trace(1);}
实在让习惯了AS1、2的朋友们郁闷。


一方面看到人家用AS3设计出来的精彩demo羡慕不已,一方面又对程序入口摸不着边际。这种尴尬我想不是看一两篇教程就能解决的。


我们需要”洗心革面”,我们需要”忘记过去”(try to forget the past)。大胆地告诉自己,onRelease=function不仅已经被”杀死”,而且根本就不是好的写法,哪怕你仍然觉得它看起来那么顺眼。大胆地告诉自己,AS3中,所有的变量、函数都属于类(对象的属性和方法),而不再属于时间轴、帧,哪怕上面列举的两行代码也可以写在时间轴上生效。


我个人建议,传统AS1/2的程序员从Flash CS3 IDE入手AS3,比较合适。因为Flash CS3的入口(开局)非常明确:Document Class(文档类)。


运行FlashCS3,打开fla文件,在IDE下面属性面板中,找到”Document Class”,填入一个名字(由于是类名,最好是首字母大写,比如MyMainClass)。然后在fla文件所在的文件夹下面建立同名的as文件。当然,也可以把fla和类文件全部分离,这就需要设定类路径(File-Publish Settings-ActionScript version:Settings)。下面可以输入类路径。我个人建议输入相对路径。相对,意即相对当前的fla文件;路径,即我们电脑文件系统中的文件夹。不写死”x:\xxx”是为了让项目可以在不同的环境上运行,也可以更好的支持多人开发。相对路径的写法就是用”.”表示当前路径,用”..”表示上一级路径。比如可以写:
“./classes/”或者”../classes/”。
这里再补充说明一下,我的建议是把原文件放在一起,输出的swf放在别的目录(通常叫做”bin”)。输出目录在刚才面板中的”format”标签下,可以把原文件放到目录”src”中,然后把swf格式的file名设置为”../bin/somefile.swf”,建议只输出swf。HTML还是自己写的好。
别看我罗嗦了这么多篇幅讲这些设置,但它们真的对于规范你的开发习惯和开发观念有好处。让你潜移默化的接受AS3的Philosophy中的”分离”思想。


补充内容: 关于Flash CS3类路径的设置,如果您希望设计自己的package。那么则需要把这个package放在classpath下面,而不要把package文件夹自己设置为classpath


切入正题,我们逐步开局:
一、建立文档类(Document Class)
现在我们可以开始建立Document Class了。Flash CS3方便地提供了一个”编辑图标”,你可以方便地打开类文件。回忆一下,上一篇文章提到关于类的书写:每一个类都应该在一个package中。我个人的理解,觉得Document Class应该在一个单独的、无具体名称的”generic”package中,即:
package
{
import flash.display.Sprite;
public class MyMainClass extends Sprite
{
public function MyMainClass()
{
init();
}
private function init()
{
// do sth
}
}
}
// We can even use some help classes
class MyMainClassHelper{}
这里,我们就成功”开局”了。
注意,这个文档类必须为public的。而辅助类则不能定义为public、private的,必须是internal的。文档类必须继承自Sprite或者MovieClip。因为这个文档类代表了这个swf,显然swf是一个需要在屏幕上渲染显示(flash.display.DisplayObject)并提供资源承载能力(flash.display.InteractiveObject)的基础容器。


二、逻辑开局(Initialize the logic)
我们所有的逻辑入口都是从这个类的构造函数开始的。AS3的loading有一些麻烦,我们暂时跳过(稍后会介绍)。
构造函数一般要保持简洁,不妨用流行的init方式开局,即在构造函数内调用一个init函数。记住一点,AS3中,”_root”已死,这里就是传统意义上的”_root”了。你看到的这个类(文档类),第一反应应该是这个swf文件(就如同你原来看到”_root”就应该反应到swf文件一样)!在这里可以找到原来我们需要的许多资源,例如我们可以找到通过loaderInfo:LoaderInfo属性(继承自DisplayObject),获取外部参数:xxx.swf?somevar=1传进来的”somevar”,也可以通过stage:Stage属性(继承自DisplayObject),来进行原来的Stage类的各种操作。我也可以用contextMenu:ContextMenu属性(继承自InteractiveObject),来控制flash右键菜单的内容。
这一切都在文档类的init以及其他所属方法中进行。所有的其它功能,可以封装成别的类、包进行”模块式”调用。


三、事件机制(The new Event System)
习惯新的事件机制所花的勇气,我认为和开局相当。我曾经热衷于xxx_mc.onRelease = function(){}的写法,而且做过N多这样的项目。然而当我真正开始用addEventListener的时候,才发现这是多么优雅的写法。优雅在哪:



  1. 统一:只有addEventListener,没有addListener、没有on(…),代码可以统一地放置。
  2. 清晰:事件处理函数作为类的方法(Methods)列举分明,试想一个跟在onXXX后面的赋值函数放在代码当众多难找。
  3. 信息翔实、准确:新的事件机制通过传递Event对象让事件的信息完整无漏地传达给接受方;函数(方法)与类绑死,Delegate终于可以光荣退休了。

四、总结



  1. 接受新的OOP开发体系:类/对象(class/object)+构造函数(constructor)+成员属性(properties)+成员方法(methods),除了这些东西以外,ActionScript没有别的存在形式!把时间轴和实例上的代码都忘记吧!
    我们要”拥抱”类的概念!AS3中所有的一切围绕着类的概念进行。swf就是一个类,用Flex开发,叫做Application,用CS3,叫做Document Class(往往继承自Sprite)。任何变量(属性)都属于一个类,MovieClip有成员属性currentScene,它是Scene类的一个实例;Sprite有成员属性contextMenu和stage,它们继承自DispatchObject类,分别是ContextMenu类和Stage类的实例。
  2. 没有_root,所有的_root有关的操作,封装到文档类中的成员函数(方法)进行。_root不再是swf的代表,取而代之的是Document Class(或者Flex中的Application Class)
  3. 功能模块化、分离:
    不要把所有的事情都塞到文档类中去做,哪怕你可以定义很多辅助类,毕竟独立出来的文件更加便于管理、集成、再使用。而且界面(图形、动画)和代码要分离(事实上AS3的Document Class和addChild的内容管理体系帮我们完成了这个操作) 。无论你是一个人搞定代码+设计还是有一个团队协作分工编程与动画。分离的好处是让你的功能更加强大和易维护。

AS3日积月累(3) – 利用AS3的图形界面开发及资源管理攻略

原文章地址:http://as3blog.com/as3/as3tip-take-care-of-resource


摒弃了attachMovie之后的AS3,采用了类似DOM的操作方式。addChild、removeChild、getChildAt等方法开始成为AS3中显示(在屏幕上渲染)、操作图形的主要方法。由于AS1、AS2完全是依赖于attchMovie的思想,因此对于传统Flash开发人员来说,转变到新的addChild的确需要下一番功夫。


由于新的“DisplayObject”在内存的使用上非常“敏感”。往往由于不良的编程习惯会造成不必要的内存泄漏,因此,我们不得不比AS1、AS2时代更加深入到内存管理了。我想,每一个Flash开发人员,包括我自己,都应该花一番功夫仔细体会“内存管理”这几个字的含义。毕竟我们学习AS3是为了开发比AS1、2时代更加先进、高效而且内存占用小的应用程序,如果还是开发一些简单的应用,也就失去我们每一个人使用AS3的意义了。


我觉得,由于GC是Flash应用程序内存管理的核心,我应该先从AS3中的Garbage Collector(简称GC)开始说起。AS3的GC功能比AS1、2中的要强大的多。然而,强大的同时,也带来了一定程度的复杂性。但是也不至于非常复杂,我觉得比C++等传统语言要容易掌握得多。


在研究内存如何回收之前,先说一下变量的创建:
在Flash中,我们每建立一个非原生变量时(Boolean, String, Number, uint, int这些是原生变量),这个变量名只是一个reference(指向,有时候也成为“引用”)而已,而并非这个变量本身。例如:
var a:int = 5; //a就是5
var b:int = a; //b是a,也就是5
a = 4;
trace(b); // 是5,而不是4!
//改变b的值,a不发生变化。反之亦然
var c:Object = {name:”aw”, blog:”www.awflasher.com/blog”}; //c只是指向一个内部的Object,为了描述方便,称其为“O”
var d:Object = c; //d指向c,指向了同一个Object“O”
c.name = “bw”
trace(d.name); //不是aw,而是bw了
//改变c也好,改变d也罢,其实是改变了那个“O”,因此改变c的时候,d的值也就变了。因此d.name已经被改变为了“bw”。


首先,明确一点,GC会按照一定的时间周期进行内存清理(memory sweep)。因此不要因为delete掉一个object后检查System.totalMemory内存就没有反应而怀疑GC是否正常。


那么GC为什么要按照一定的时间周期进行清理呢。这还要得从GC的具体工作原理说起。


GC的两个回收体系:
1、“Reference Counting” – 引用计数器
这个体系是自从AS1时代就有的体系,它的工作原理非常简单:系统计算每一个对象被指向,或者说引用的次数。比如
var a:Object = {name:”aw”, blog:”www.awflasher.com/blog”};
这时候,这个Object(我们仍然称为“O”),有一次引用,它的引用计数器为1(来自a)。
我们再建立一个对象b,并指向到a:
var b:Object = a;
这时候“O”引用计数器变为了2(来自a、b)
我们删除一个,比如先删除b:
delete b;
这时候引用计数器为(2-1=1)1,GC不操作
再删除另外一个a:
delete a;
“O”引用计数器变为(1-1=0)0,GC出面干掉这个对象。
这套体系很轻便,CPU压力较小。但是它也有缺陷。当我们的对象内部互相引用的时候,麻烦就来了。例如:
var a:Object = {name:”aw”, description:”unknown”};
// 建立一个对象a,仍然假设内部对象为“O”,这时O的引用次数为1
var b:Object = {nameObj:a, url:”awflasher.com”};
// b引用了a,同时创建了新的内部对象“P”。这时O的引用次数为2,P为1
a.myDescription = b;
// a的myDescription属性指向到了b。这样,P的引用次数也为2了。
// 是不是有点头晕?静下来,画个图慢慢看看:)
delete a;
delete b;
两次delete操作后,O、P的引用次数都是1,它们将继续占用你的内存。“Reference Counting” 体系无能为力了。


2、“Mark Sweeping” – 标记清除法则
GC的第一种机制“Reference Counting”,在FlashPlayer8之前是GC唯一的机制。FlashPlayer6和7由于引入了复杂的OOP开发模式,尤其是7引入了类似Java、C++等强大OOP语言的语法。利用Flash设计的复杂项目越来越多。由于Flash开发人员大多不了解GC,而Java、C++的开发人员又已经习惯了强大的GC(无论是自动的还是手动的)。因此FlashPlayer6、7的内存问题开始浮现出来。
Okay,Flash Player8引入了新的“Mark Sweeping”机制。我想这也是当年Marcomedia(Adobe)急于推出Player8的原因吧!(还记得当年Flash8的介绍视频么,效率提高是一个革命性的改进)
下面就来讲述“Mark Sweeping” – 标记清除法则的工作原理。
FlashPlayer会从root开始,遍历系统的每一个变量,并对有指向的对象之间,记录一次联系。在遍历结束之后,凡是与root不相联系的对象,被FlashPlayer无情地干掉:)
Okay,回到刚才的例子,当我们delete a,并delete b之后,root与O、P就划清了界线。这时候,GC就可以进行一次肃清了。
然而,由于这种“Mark Sweeping”要遍历所有的对象,因此非常消耗资源。这也就回到了当初的问题:“GC为什么要按照一定的时间周期进行清理“ – 因为不能给CPU造成太大的负担。
我个人猜测,GC的内部清除策略应该是在某一次事件(例如delete)发生后,在CPU比较空闲、RAM分配相对合理的情况下执行的。


3、关于delete。Gskinner《Understanding Delete》说的很清楚:
delete在AS3中,只能删除dynamic类的对象实例的属性/方法了。这意味着你不能像AS2那样用delete随意删除成员了。当然,替代方法还是有的:把成员设置为null即可。例如myobj.name=null;


Okay,不要以为有了“Mark Sweeping”就万事大吉了。由于GC不会即时进行,因此你的对象会在一段时间内“阴魂不散”!对于一个追求完美的开发人员来说,这意味着它们内部的某些机制会在被删除之后继续工作:AS语句会继续执行、声音会继续播放、事件会继续触发!


KirupaForum有网友说,“All you got to do is pray the garbage collector doesn’t break down.”,确实,如果一个应用程序要运行上一个多小时,那么慢慢流逝的内存会让你的用户对你的产品失望(例如游戏)。因此我们需要有一个良好的资源管理策略。