遍历动态属性
作为一个AS3开发者,经常会与循环打交道,今天的主角将是我们常用的for…in和for each….in。如果您觉得今天讲的话题太基础没什么可看的,那么,请一路走好~
首先,for…in和for each…in都可以用来遍历动态对象。这里需要详细地讲一下什么是动态对象,很多道友对于动态对象的概念不是特别了解。动态对象就是可以在运行时为它添加属性的对象,在它的类声明语句中使用
dynamic
关键字修饰。下列代码演示了如何创建一个动态类DynamicClass:
package { public dynamic class DynamicClass { } }
是的,只需要在class前面使用dynamic关键字修饰即可让类变成动态类,之后你可以在运行时为其创建/删除属性,如下:
var d:DynamicClass = new DynamicClass(); d.property1 = "123"; d.property2 = 250; delete d.property2;
即使在DynamicClass类中并没有使用public var property1:String这样的语句来创建属性,你也可以对它进行动态的属性创建或删除(使用delete关键字)。如果道友你的基础够扎实,你应该知道,AS3中我们还可以使用方括号来创建属性,如上述代码中创建property1属性的代码其实可以写成:
d["property1"] = 123;
注意哦,使用方括号方式创建属性时,属性名最好使用字符串(被双引号包裹起来的),若是使用整形也没问题,如d[1] = 123。
在AS3中,动态类不多,用得较多的动态类是Object、Array、Dictionary、MovieClip及新成员Vector(需要注意的是,Vector是一个及特殊的动态类,我们没有办法为其进行动态属性添加/删除)。当你参看语言参考时,在最上面的类描述信息中可以注意一下是否存在
dynamic
关键字。
对于数组Array来说,我们要慎用属性动态创建的功能,看以下例子先:
var ary:Array = [1,2,3]; ary.property1 = "555"; for each(var value:* in ary) { trace(value);//输出:1,2,3,555 }
大家应该看出来我为什么要这么说了吧?为Array对象动态创建属性会影响for…in及for each…in遍历的准确性,我们只想遍历那些被push到数组中的元素,却把动态属性给带进来了……此时你最好还是改用for(var i:int=0; i<ary.length; i++)的方式来遍历比较好。
那么,接下来让我们言归正传,使用for…in和for each…in都能够遍历到动态对象中的动态属性。这里需要注意一点哦,所谓的动态属性并不是指动态类中的全部属性,而是在运行时才加上的属性。例如我们在之前的DynamicClass中声明一个属性property1先:
public dynamic class DynamicClass { public var property1:int; }
之后我们在运行时动态创建另一个属性property2。
var d:DynamicClass = new DynamicClass(); d.property2 = 0;
那么此时我称属性property1为固有属性(不用“静态属性”这个名字,为了避免和static的属性相混淆),而property2为动态属性,使用for…in或for each…in只能遍历到DynamicClass的property2属性,也就是动态对象的动态属性。测试例子如下
var d:DynamicClass = new DynamicClass(); d.property2 = 1; for(var p:String in d) { trace(p);//仅输出property2 } for each(var value:* in d) { trace(value);//仅输出1 }
从这个例子里面我们也可以看出for…in和for each…in的不同之处,前者遍历的是动态属性的键,而后者遍历的是动态属性的值。属性的键一定是一个String字符串类型(即使你使用方括号加整形的形式来创建属性也依然如此,如d[1] = 123,虽然此处你为动态对象d设置的属性键是一个1的整形,但是AS3会自动把此整形转换为字符串,它约束了属性键必须是字符串),而值却可能为任意类型,因此我在上述代码中在for…in循环中将p变量设置为String类型,而在for each…in循环中将value变量设置为任意类型。这里需要提一下Dictionary这个动态类,因为它比较有个性,他与众不同的地方在于它能够允许一个复杂对象作为属性键,在下面的代码中,它把一个Sprite对象作为了属性键:
var d:Dictionary = new Dictionary(); var sp:Sprite = new Sprite(); d[sp] = 1; for(var p:* in d) { trace(p);//输出:[object Sprite] }
此时注意了,虽然你知道d的属性键为一个Sprite对象,但是你没办法在for…in循环中把遍历变量p设置为Sprite类型,如果你这么做的话编译器会报错。
for(var p:Sprite in d)//报错!1067: Implicit coercion of a value of type String to an unrelated type flash.display:Sprite { trace(p); }
此时我们也不可能把循环变量p设置为String呀,所以只能用通配符*来描述p。
通过for…in,和for each…in我们可以节省大量的工作量,比如从一个动态对象中拷贝属性的时候。假设现在我有一个类A,它里面有p1,p2….很多的固有属性,这些属性的值都将从后台服务器获取,那么后台服务器返回给我的数据都会存在一个拥有相同属性p1,p2….的Object对象里面,此时我将从这个Object对象中把同名属性拷贝到我的A对象中,此时若是我傻傻地写a.p1 = object.p1; a.p2 = object.p2….的话实在是太麻烦了,我他妈要遍历!那么此时你就需要为类A创建一个拷贝属性的方法,如下所示:
public function copyValue(value:Object):void { for(var p:String in value) { if(hasOwnProperty(p)) { this[p] = value[p]; } } }
有了这个方法后我们只需要对A的对象调用此方法即可一次性拷贝完全部的同名属性。
虽然这种遍历属性的方式非常便捷,但是唯一的遗憾就是它没办法遍历出对象的固有属性,若是我要对比一个非动态类的两个对象属性,看看它们的全部属性值是否一样的话应该怎么样做比较方便呢?答案是将非动态类转换成Object动态类然后遍历,使用ByteArray可以帮助我们做到这种转换。例如我们现在有一个类A的声明如下:
package { public class A { public var property1:String; public var property2:String; public var property3:String; public var property4:String; public var property5:String; } }
它不是一个动态类,我现在想比对两个A对象它们的property1-property5的值是否全部一样。那么就可以先通过ByteArray将其中一个对象转换为Object动态类。
public function isEqual( comparer:A ):Boolean { //将comparer转换为dynamic动态类以便可以使用for..in循环来遍历属性 var byteAry:ByteArray = new ByteArray(); byteAry.writeObject(comparer); byteAry.position = 0; var obj:Object = byteAry.readObject(); //逐个比较双方的属性值,若有一个属性值不一致则返回false for(var p:String in obj) { if( this[p] != obj[p] ) { return false; } } return true; }
这个方法中还存在着一个潜在的问题就是,若A类中存在复杂类型的属性,如Array,Object这种的属性的话是不能简简单单地使用不等号(!=)来判断是否相等的,因此我们在这个方法中还需要设置一个参数,通过这个参数来在遍历时剔除那些没办法通过不等号来比较的属性。此时,isEqual方法就变成了:
public function isEqual( comparer:A, ignoreProperty:Array=null ):Boolean { …… //逐个比较双方的属性值,若有一个属性值不一致则返回false if( !ignoreProperty )ignoreProperty = []; for(var p:String in obj) { if( ignoreProperty.indexOf(p) == -1 && this[p] != obj[p] ) { return false; } } return true; }
这样就可以了,如果A类中存在一个键叫ary的Array属性的话,我们就可以先调用a1.isEqual(a2, “ary”)来比较除ary之外的属性,若是该方法返回了true,则接着使用循环来逐个比较两个ary数组中各个元素是否完全一致。
对于固有属性,基本上除了使用反射(用flash.utils.describeType方法来获取该类的描述XML)之外没有其他办法能够遍历,但是使用反射获取到的类描述信息XML中会包含类中全部继承自父类的属性,有时候这些冗余信息会让我们没有办法获得当前要遍历类它独有的属性。因此在实际开发中我们尽量避免去遍历固有属性,这会让你实现起来非常头大,还是用for…in, for each…in遍历遍历动态属性算了。
最后提一句,我们在写代码的时候还是尽量避免自定义动态类,因为这比较耗性能(虽然在运行应用的时候感觉不到)而且可维护性较差。