如何在AS3中实现枚举器?我想循环使用for each
循环的对象,这样我就可以遍历树或显示列表中的所有对象。
我认为Proxy类可以派上用场,使用nextName,nextNameIndex,nextValue和getProperty方法。
答案 0 :(得分:1)
这是我的显示列表枚举器的实现,它将遍历深度优先庄园中的DisplayObjectContainer的子节点:
package flos.utils
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.utils.flash_proxy;
import flash.utils.Proxy;
import flos.system.IDisposable;
public class DisplayListEnumerator extends Proxy implements IDisposable
{
private var list:Vector.<DisplayObject>;
private var root:DisplayObjectContainer;
public function DisplayListEnumerator( root:DisplayObjectContainer )
{
this.root = root;
}
public function dispose():void
{
root = null;
}
private function get disposed():Boolean
{
return root == null;
}
private function enumDisplayList():void
{
var _childIndex:int = 0;
var _childIndexStack:Vector.<int> = new Vector.<int>();
var _currentContainer:DisplayObjectContainer = root;
var _current:DisplayObject;
list = new Vector.<DisplayObject>();
enumerate: do
{
for (var i:int = _childIndex; i < _currentContainer.numChildren; i++)
{
_current = _currentContainer.getChildAt( i );
list.push( _current );
if (_current is DisplayObjectContainer)
{
_childIndexStack.push( i + 1 );
_childIndex = 0;
_currentContainer = _current as DisplayObjectContainer;
continue enumerate;
}
}
if (_currentContainer == root)
break enumerate; //break when we finish enumerating the root element
else
{
_childIndex = _childIndexStack.pop();
_currentContainer = _currentContainer.parent;
}
} while (true);
}
override flash_proxy function nextNameIndex( index:int ):int
{
if (disposed)
throw new Error( "Enumerator disposed." );
if (index == 0) //initial call, cache the current display list in a vector
{
enumDisplayList();
return 1;
}
else if (index < list.length)
return index + 1;
else
return 0;
}
override flash_proxy function nextName( index:int ):String
{
if (disposed)
throw new Error( "Enumerator disposed." );
var i:int = index - 1;
if (i > -1 && i < list.length)
return String(i);
else
return null;
}
override flash_proxy function nextValue( index:int ):*
{
if (disposed)
throw new Error( "Enumerator disposed." );
var i:int = index - 1;
if (i > -1 && i < list.length)
return list[i];
else
return undefined;
}
}
}
由于枚举器扩展了Proxy类并实现了与枚举相关的方法,因此可以在每个循环中使用它,如下所示:
import flos.utils.DisplayListEnumerator;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
var i:int = 0;
for each (var d:DisplayObject in (new DisplayListEnumerator( root as DisplayObjectContainer )))
trace( (i++) + " " + d.name );
当然,这可以在没有预先索引显示列表的情况下实现,但这需要额外的检查,例如确保调用nextNameIndex中的索引不会乱序调用(并且实际上增加了一个从调用到调用),就像我在上面的实现中一样,以非递归的方式维护一个枚举在每个级别的堆栈,以及任何不将显示列表索引为单个操作的解决方案如果在枚举期间显示列表完全改变,则存在错误**或甚至无限循环的风险。我在这里提供的实现在枚举时获取显示列表的快照可能是最稳定的。
**我自己也实现了非预索引版本,我可以告诉你它们实际上不太稳定,特别是如果你正在进行任何类型的调试,因为调试器会尝试枚举枚举器的属性。你进入代码,立即破坏活跃的枚举状态,所以我在上面提供的实现(特别是一种在枚举开始时一步预缓存显示列表的实现)实际上可能是唯一稳定的方式实现这一点。
这是非预索引版本,它不是在枚举生成时索引整个显示列表,而是在每个循环的每次迭代中逐节点地遍历显示层次结构。这种类型的实现受到调试器的“本地”窗口的干扰,然而,它试图“枚举枚举器”(Visual Studio实际上警告你的东西),所以我建议不要使用以下类型的实现:
package flos.utils
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.utils.flash_proxy;
import flash.utils.Proxy;
import flos.system.IDisposable;
public class DisplayListEnumeratorRealtime extends Proxy implements IDisposable
{
private var list:Vector.<DisplayObject>;
private var root:DisplayObjectContainer;
private var _enumIndex:int;
private var _parentStack:Vector.<DisplayObjectContainer>;
private var _childIndexStack:Vector.<int>;
private var _childIndex:int;
private var _current:DisplayObject;
private var _currentContainer:DisplayObjectContainer;
public function DisplayListEnumeratorRealtime( root:DisplayObjectContainer )
{
this.root = root;
}
public function dispose():void
{
root = null;
}
private function get disposed():Boolean
{
return root == null;
}
private function reset():void
{
_childIndexStack = new Vector.<int>();
_parentStack = new Vector.<DisplayObjectContainer>();
_currentContainer = root;
_current = null;
_enumIndex = 0;
_childIndex = 0;
}
private function pushChild():void
{
_parentStack.push( _currentContainer );
_childIndexStack.push( _childIndex + 1 );
_childIndex = 0;
_currentContainer = _current as DisplayObjectContainer;
}
private function popChild():void
{
_childIndex = _childIndexStack.pop();
_currentContainer = _parentStack.pop();
}
override flash_proxy function nextNameIndex( index:int ):int
{
if (disposed)
throw new Error( "Enumerator disposed." );
if (index == 0) //initial call, cache the current display list in a vector
reset();
if (index == _enumIndex) //ensure expected index is passed
{
_enumIndex = index + 1;
processNextChild: do
{
if (_childIndex < _currentContainer.numChildren)
{
_current = _currentContainer.getChildAt( _childIndex );
if (_current is DisplayObjectContainer)
{
pushChild();
return _enumIndex;
}
_childIndex++;
return _enumIndex;
}
else if (_parentStack.length > 0)
{
popChild();
continue processNextChild;
}
break;
} while (true);
if (_currentContainer == root)
return 0; //enumeration is complete when enumeration of the root element is complete
return _enumIndex;
}
else
{
throw new Error( "Enumeration index called out of order." );
}
}
override flash_proxy function nextName( index:int ):String
{
if (disposed)
throw new Error( "Enumerator disposed." );
if (index == _enumIndex)
return String(_enumIndex);
else
throw new Error( "Enumeration index called out of order." );
}
override flash_proxy function nextValue( index:int ):*
{
if (disposed)
throw new Error( "Enumerator disposed." );
if (index == _enumIndex)
return _current;
else
throw new Error( "Enumeration index called out of order." );
}
}
}