`
阅读更多

一. 事件机制

1. as常见而且内建的解耦机制,解决以下问题

 a) 传统回调机制只能一对一,产生强耦合

 b) 传统机制使用不统一的方式处理不同对象的异步操作

 

2. actionscript使用事件派发器EventDispatcher类实现事件派发,通常包括三对象

 

a) 事件对象通常是Event的子类,用于指定监听事件类型(通常是常量字符串成员)和作为监听器的参数传入(函数参数,通过它获取上下文信息实现解耦)。

 

b) 事件派发器在监听器中被成为target,是广播事件的对象,支持多监听器注册addEventListener。在监听器中有两种获取方式:currentTaget和target,currentTaget是开始时调用addEventListener注册事件对象,target则是处于事件真正发生的对象

 

c) 监听器。可以是成员函数,也可以是匿名函数,是处理事件的逻辑部分

 

3. 注册和删除事件。一般习惯是addEventListener和removeEventListener需要成对出现。个人认为即使不必要,但最好这么做。由于涉及gc回收,一些匿名监听器在addEventListerner中指定弱引用(由as负责释放)

 

4.AddEventListener的参数解析:

a)事件类型:一般是事件对象对应的字符串常量,与监听器的参数对应。例如Event.COMPLETE后的监听器的参数就是e:Event。

b)监听器:一般是成员变量,解决派发器与监听器之间的引用问题(监听器不会被无端端地GC掉),但如果是匿名函数,将因为与注册者存在引用而无法gc掉注册者和监听器。

c)在哪个阶段调用监听器函数:有几种情况(假设this是m_object的显示容器)

 其一:this.addEventListener(xx, xx)冒泡阶段,所有发生在自己和addChild的显示对象(如果是ui事件)的事件都触发监听器。在监听器中target是发生事件的对象,而currentTarget是this(容器自身)

 其二:m_object.addEventListener(xx,xx)目标阶段,也是最常见的用法,用于响应m_object的事件,而不会让其父容器听到(如果是ui事件)。此时target==currentTarget

 其三:this.addEventListener(xx,xx,true)捕获阶段, 从显示对象的根向目标方向遍历执行监听器,但不会到达目标和向上冒泡。较少用,要用的话一般在监听器中使用stopImmediatePropagation让事件仅由容器this来响应

 其四:同时使用this.addEventListener(xx, xx)和this.addEventListener(xx,xx,true),可以同时监听捕获和冒泡目标的事件。

 

d)优先级,用于决定哪个监听器函数先调用,一般不用(因为默认使用目标阶段,较少出现同一种事件注册多个不同的监听器)

 

e)弱引用,用于那些无法正常removeEventListener的情况(例如注册对象被赋值为null失去引用)。缺点是弱引用无法控制其失去监听能力的时机。另一种情况,你希望监听器被gc(例如你不停地注册匿名监听器而不removeListener,导致大量的匿名函数不被gc,因为有引用存在)如果你习惯用成员函数作监听器,推荐用removeListener(立刻生效,不像弱引用那样)当然你监听ui事件而且是子级显示对象例如this.button.addEventListerner,但button突然被赋值为null导致不能removeListener,可以考虑用弱引用(一般没必要,因为this的外部引用不会在事件发生的时候被赋值为null被gc掉,况且this会和成员函数一起被gc。this.button也一般不会被赋值为空。即便你要让this.button=null,你也有机会在此之前使用removeListener,除非你懒得去做)

 

 

补注:个人认为(不一定正确),在这种场合使用弱引用:

* 你的事件不是添加在自己或成员对象上(最常见的是对stage添加键盘事件)

* 你的事件不是注册到当前类的成员函数(最常见的是匿名闭包)

* 你的事件不知道在什么时候remove(例如你不是用对称的方式添加删除事件)

 

有些特殊情况(好像?)不应该使用弱引用

* 用Event.ADDED_TO_STAGE和Event.REMOVED_FROM_STAGE的响应函数中对称地添加删除事件(未验证)

* 有些事件如果只允许发生一次(你希望你的响应函数要么不执行,要么就只执行一次),你可以立刻在响应函数中删除这个事件,例如在构造函数中:

addEventListener(Event.ADDED_TO_STAGE, onAdd);

因为即便Event.ADDED_TO_STAGE被派发多于一次,

也不会影响逻辑(因为你就是希望onAdd仅执行一次,除非对象被释放)

* 有些事件所添加的对象和注册函数都是持久的,

就可省略事件的删除,例如文档类的单实例对象、stage以及它们的成员函数。

 

 

二 GC机制

由于fp的GC向来都是写as的人诟病对象,所以大多数人都会想尽办法去防止代码中出现内存泄漏的情况(因为fp会因为内存太大而崩溃)虽说这不是什么好事(很多语言都竭力让程序员不用考虑或者提供API应付这个问题,但是as就是不让你控制),一些GC问题实际上迫使程序员改良代码以提高as运行的效率和稳定性,甚至可以让代码更有条理。

 

1. fp9的MouseEvent.CLICK问题。从我测试看来,似乎fp9的内存回收特别慢,尤其是当你想用MouseEvent.CLICK去释放某个显示对象的时候(详细请参考http://joshblog.net/2009/04/30/combine-buttonmode-true-and-a-mouse-click-to-leak-memory/)可喜的是这个貌似bug的问题到了fp10就没了。虽说可以用buttonMode = false去解决这个无法gc的恐怖bug,但个人尝试觉得最好用MouseEvent.MOUSE_DOWN代替(MOUSE_UP也会导致这个bug出现)结论是,要么大家都升级到fp10,要么就别用这个让人困惑的CLICK事件。

 

2. fp10的gc速度比fp9快。目前看来似乎是这样(估计快10倍)但内存承载能力就差不多(估计底层没有改什么)而且内存负载能力似乎跟系统物理内存大小有关(?)

 

 

 

 

三、序列化机制
广泛用于网络协议的实现(因为网络通信通常是流式)

1. 预先序列化:把某种没有结构的数据直接传递给模型对象(可能是构造函数),对于复杂的数据尤其有用。这种序列化很常见,例如使用XMLSocket通信,获取的data可以直接转换为XML对象。另外,as的Socket支持AMF对象的直接传递,这点颇像Java的序列化。常见的有XMLSocket,XML字符串,JSON字符串等。

 

2. 惰性序列化:惰性序列化并不是说懒人用的,相反这种序列化消耗更多的时间,代码量更大。它适合于不复杂的数据。宗旨是根据要求序列,通过一系列的getter和setter(或者属性赋值)把数据转移到另一个对象。另外,这种机制支持伪的数据域(就是说通过计算获得的,而非实际存储的数据)。一般用于Socket, ByteArray,URLStream对象等。

 

3. 代理序列化:很像惰性序列化,但它是cache加载的,例如模型数据携带原始数据的引用,每次get的时候判断模型数据是否有数据,有就直接取出,没有就读取原始数据。每次set则同时更新模型和原始数据。这种方法对复杂模型(例如xml数据)或者get时加载的数据有效(例如通过http访问rest服务器的数据)。对于简单结构来说,这么做似乎有点不划算了。

 

4. 反射机制的序列化:指使用文本协议和flash.utils.Proxy实现的惰性序列化。每个get函数将由Proxy的子类通过重载callProperty获取(被统一为字符串参数)。《Actionscript 3 设计模式》一书介绍了这种序列化(它的例子是用http加载数据)详细请看http://rightactionscript.com/aas3wdp/第六章的例子。它可以结合上面提到的多种序列化方法。

 

 

 

 

四、状态模式
围绕状态常量进行编程的一种模式(例如游戏分加载配置,加载数据,初始化,开始四种状态)一般会派生出以下几种用法:

1. 状态常量:通常是一些字符串或者整数常量。

 

2. 状态变量:一个记录当前状态值的变量,在状态常量的范围内

 

3. 循环事件内的状态机:一种典型的情况,在Event.ENTER_FRAME事件监听器中加入switch,通过return保持状态(可以使用if实现轮询),通过为状态变量赋值实现状态改变。执行迁移瞬间的操作(状态变量赋值前)和保持状态的空闲操作(return前)。

 

4. 状态迁移:虽说是迁移,实际是依赖于状态机的间接迁移(不是立刻发生发生改变,需要等到Event.ENTER_FRAME之类的事件发生)。对于在Event.ENTER_FRAME之类的事件中调用的函数来说,给状态变量赋值都是在状态机内部做(你也可以挪到状态对象里面做,但实际上还是在Event.ENTER_FRAME里做)。不过,状态迁移(状态变量赋值)还可能发生在别的地方,例如一些异步事件,如鼠标,键盘,定时器事件,就不是在状态机里面了。

 

5. 状态对象:类似与状态变量,但它成了接口。接口的意义在于你可以实现很多个实例类,然后在里面定义状态迁移,以及不同状态下的环境值(如果你在不同状态下读同一类的对象)。通过多态性,你可以写出统一的处理函数。当然,状态对象不是必须有的。

 

参考资料:

http://rightactionscript.com/aas3wdp/

 

 

 

五 AS3特殊用法摘录:

 

1. ||=的使用

例如bracket ||= {start: '[', end: ']'};

等效于if(bracket != null) bracket = {start: '[', end: ']'};

参考自:libspark.as3.Dumper

 

2. arguments.callee

a.addEventListener(Event.COMPLETE, function (event:Event):void{
       event.currentTarget.removeEventListener(Event.COMPLETE, arguments.callee);
});

 

 http://www.adobe.com/livedocs/flash/9.0/ActionScriptLangRefV3/arguments.html

 

用这种方法可以获取当前执行的函数(闭包),无需保存为局部或成员变量。

 

3. MouseEvent::updateAfterEvent

常常用于鼠标跟随的平滑显示(在鼠标事件内实现):

        private function onMove(event:MouseEvent) : void
        {

             //改变鼠标sprite的x和y值

             event.updateAfterEvent();
        }

 

4. DisplayObject::cacheAsBitmap

一个可读写的属性, 提高非动画显示对象的显示速度,消耗内存。

 

 

5. 闭包的局部作用域用法

因为ActionScript和旧版的JavaScript一样,不支持局部作用域,如果要保证局部作用域,可以这样写:

 

package  
{
	import flash.display.Sprite;
	/**
	 * ...
	 * @author 
	 */
	public class TestClosure extends Sprite
	{
		private var time:int = 0;
		
		public function TestClosure() 
		{
			(function():void {
				trace("time:", time);
			})();
			
			var time:int = 1;
			(function():void {
				var time:int = 2;
				trace("time:", time);
			})();
			(function():void {
				trace("time:", time);
			})();
			/**
			 * 输出:0, 2, 1
			 * 
			 */
		}
	}
}

 注意,如果闭包使用对象属性,可能导致堆栈错误,所以尽量不要在闭包中使用对象属性。

另外,function外的小括号不能省,因为那是用来消除歧义的(和JavaScript一样)。

 

 

 

 

6. 使用ByteArray.readByte和wirteByte需要小心,

因为处理的都是有符号整数,读取时需要&0xFF,否则uint值会很大。

 

			var test_bytes:ByteArray = new ByteArray();
			test_bytes.writeByte(0xFA);
			test_bytes.position = 0;
			if (test_bytes.bytesAvailable > 0)
			{
				var result:uint = test_bytes.readByte() & 0xFF;
				trace("result:", result.toString(16));
			}
			test_bytes.position = 0;
			if (test_bytes.bytesAvailable > 0)
			{
				var result2:uint = test_bytes.readByte();
				trace("result2:", result2.toString(16));
			}
			//输出
			//result: fa
			//result2: fffffffa

 

20110304:

上面的代码太麻烦,

ByteArray有一个方法叫readUnsignedByte可以直接读取无符号字节 

 

 

7. 尽量不要在Socket的构造函数中传入域名和端口号。应该使用connect函数打开端口。

另外不要和Java的Socket混淆,AS3的socket是基于事件的异步操作,

不可立刻读写(不同于Java的阻塞式Socket读写),

而应该使用事件(见Actionscript3语言和组件参考的例子程序)。

 

 

20110424:

8. 执行beginBitmapFill后必须drawRect或其它draw操作方可生效,例如

 

 

			shape02.graphics.beginBitmapFill(bitmap01, null, false, true);
			shape02.graphics.drawRect(0, 0, bitmap01.width, bitmap01.height);
			shape02.graphics.endFill();

 

20110520:

9. AS3的显示对象平滑显示

AS3显示对象系统提供平滑显示(smoothing)的方法,不过仅限于Bitmap(BitmapData也可,不过BitmapData不属于显示列表的管理范围)

Bitmap的局限性在于它仅能应用于Loader和BitmapData,不能对那些使用addChild的显示对象使用draw方法(因为得不到正常的叠加效果)。

也就是说最好对Loader使用Bitmap,然后对Bitmap使用smoothing,以达到平滑效果,

下面的代码假设Loader已经完成对图像文件的加载:

 

 

		/**
		 * 必须在Loader完全加载完后才可用
		 * @param	container
		 * @param	layer
		 * @param	alphaLayer
		 */
		private static function addAlphaChild(container:DisplayObjectContainer, layer:Loader, alphaLayer:Loader = null):void
		{
			var sprite:Sprite = new Sprite();
			sprite.blendMode = BlendMode.LAYER;
			//
			var bmpLayer:Bitmap = new Bitmap(Bitmap(layer.content).bitmapData);
			bmpLayer.smoothing = true;
			sprite.addChild(bmpLayer);
			//
			if (alphaLayer)
			{
				var bmpAlphaLayer:Bitmap = new Bitmap(Bitmap(alphaLayer.content).bitmapData);
				bmpAlphaLayer.blendMode = BlendMode.ALPHA;
				bmpAlphaLayer.smoothing = true;
				sprite.addChild(bmpAlphaLayer);
			}
			//
			container.addChild(sprite);
		}

 

另外,如果对Bitmap对象的bitmapData属性使用draw方法。

smoothing=true应该放在draw后面,否则绘画内容不会出现平滑。

不推荐对Bitmap对象的bitmapData属性使用draw,

因为很可能不小心draw了使用过addChild的显示对象。

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics