扩展 Array 类

Flash Player 9 和更高版本,Adobe AIR 1.0 和更高版本

Array 类是少数不是最终类的核心类之一,也就是说您可以创建自己的 Array 子类。本部分提供了创建 Array 子类的示例,并讨论了在创建子类过程中会出现的一些问题。

如前所述,ActionScript 中的数组是不能指定数据类型的,但您可以创建只接受具有指定数据类型的元素的 Array 子类。以下部分中的示例定义名为 TypedArray 的 Array 子类,该子类中的元素限定为具有第一个参数中指定的数据类型的值。这里的 TypedArray 类仅用于说明如何扩展 Array 类,并不一定适用于生产,这有以下若干原因。第一,类型检查是在运行时而非编译时进行的。第二,当 TypedArray 方法遇到不匹配时,将忽略不匹配并且不会引发异常;当然,修改此方法使其引发异常也是很简单的。第三,此类无法防止使用数组访问运算符将任一类型的值插入到数组中。第四,其编码风格倾向于简洁而非性能优化。

注: 可以使用此处介绍的方法创建指定类型的数组。但是,更好的方法是使用 Vector 对象。Vector 实例是真正的指定类型的数组,在性能和其他一些方面比 Array 类或任何子类都好。本文所进行的讨论旨在演示如何创建 Array 子类。

声明子类

可以使用 extends 关键字来指示类为 Array 的子类。与 Array 类一样,Array 的子类应使用 dynamic 属性。否则,子类将无法正常发挥作用。

以下代码显示 TypedArray 类的定义,该类包含一个保存数据类型的常量、一个构造函数方法和四个能够将元素添加到数组的方法。此示例省略了各方法的代码,但在以后的部分中将列出这些代码并详加说明:

public dynamic class TypedArray extends Array 
{ 
    private const dataType:Class; 
 
    public function TypedArray(...args) {} 
     
    AS3 override function concat(...args):Array {} 
     
    AS3 override function push(...args):uint {} 
     
    AS3 override function splice(...args) {} 
     
    AS3 override function unshift(...args):uint {} 
}

由于本示例中假定将编译器选项 -as3 设置为 true ,而将编译器选项 -es 设置为 false ,因而这四个被覆盖的方法均使用 AS3 命名空间而非 public 属性。这些是 Adobe Flash Builder 和 Adobe Flash Professional 的默认设置。

如果您是倾向于使用原型继承的高级开发人员,您可能会在以下两个方面对 TypedArray 类进行较小的改动,以使其在编译器选项 -es 设置为 true 的情况下进行编译。一方面,删除出现的所有 override 属性,并使用 public 属性替换 AS3 命名空间。另一方面,使用 Array.prototype 替换出现的所有四处 super

TypedArray 构造函数

由于此子类构造函数必须接受一列任意长度的参数,从而造成了一种有趣的挑战。该挑战就是如何将参数传递给超类构造函数以创建数组。如果将一列参数作为数组进行传递,则超类构造函数会将其视为 Array 类型的一个参数,并且生成的数组长度始终只有 1 个元素。传递参数列表的传统处理方式是使用 Function.apply() 方法,此方法将参数数组作为第二个参数,但在执行函数时将其转换为一列参数。遗憾的是, Function.apply() 方法不能和构造函数一起使用。

剩下的唯一方法是在 TypedArray 构造函数中重新创建 Array 构造函数的逻辑。以下代码说明在 Array 类构造函数中使用的算法,您可以在 Array 子类构造函数中重复使用此算法:

public dynamic class Array 
{ 
    public function Array(...args) 
    { 
        var n:uint = args.length 
        if (n == 1 && (args[0] is Number)) 
        { 
            var dlen:Number = args[0]; 
            var ulen:uint = dlen; 
            if (ulen != dlen) 
            { 
                throw new RangeError("Array index is not a 32-bit unsigned integer ("+dlen+")"); 
            } 
            length = ulen; 
        } 
        else 
        { 
            length = n; 
            for (var i:int=0; i < n; i++) 
            { 
                this[i] = args[i]  
            } 
        } 
    } 
}

TypedArray 构造函数与 Array 构造函数中的大部分代码都相同,只在四个地方对代码进行了改动。其一,参数列表中新增了一个必需的 Class 类型参数,使用此参数可以指定数组的数据类型。其二,将传递给构造函数的数据类型分配给 dataType 变量。其三,在 else 语句中,在 for 循环之后为 length 属性赋值,以使 length 只包括相应类型的参数。其四, for 循环的主体使用 push() 方法的被覆盖版本,以便仅将正确数据类型的参数添加到数组中。以下示例显示 TypedArray 构造函数:

public dynamic class TypedArray extends Array 
{ 
    private var dataType:Class; 
    public function TypedArray(typeParam:Class, ...args) 
    { 
        dataType = typeParam; 
        var n:uint = args.length 
        if (n == 1 && (args[0] is Number)) 
        { 
            var dlen:Number = args[0]; 
            var ulen:uint = dlen 
            if (ulen != dlen) 
            { 
                throw new RangeError("Array index is not a 32-bit unsigned integer ("+dlen+")") 
            } 
            length = ulen; 
        } 
        else 
        { 
            for (var i:int=0; i < n; i++) 
            { 
                // type check done in push()  
                this.push(args[i]) 
            } 
            length = this.length; 
        } 
    } 
}

TypedArray 覆盖的方法

TypedArray 类覆盖前述四种能够将元素添加到数组的方法。在每种情况下,被覆盖方法均添加类型检查,这种检查可以防止添加不正确数据类型的元素。然后,每种方法均调用其自身的超类版本。

push() 方法使用 for..in 循环来遍历参数列表,并对每个参数执行类型检查。可以使用 splice() 方法,从 args 数组中删除所有不是正确类型的参数。在 for..in 循环结束后, args 数组仅包含 dataType 类型的值。然后对更新后的 args 数组调用 push() 的超类版本,如以下代码所示:

    AS3 override function push(...args):uint 
    { 
        for (var i:* in args) 
        { 
            if (!(args[i] is dataType)) 
            { 
                args.splice(i,1); 
            } 
        } 
        return (super.push.apply(this, args)); 
    }

concat() 方法创建一个名为 passArgs 的临时 TypedArray 来存储通过类型检查的参数。这样,便可以重复使用 push() 方法中的类型检查代码。 for..in 循环用于遍历 args 数组,并为每个参数调用 push() 。由于将 passArgs 指定为类型 TypedArray,因而将执行 push() 的 TypedArray 版本。然后, concat() 方法将调用其自身的超类版本,如以下代码所示:

    AS3 override function concat(...args):Array 
    { 
        var passArgs:TypedArray = new TypedArray(dataType); 
        for (var i:* in args) 
        { 
            // type check done in push() 
            passArgs.push(args[i]); 
        } 
        return (super.concat.apply(this, passArgs)); 
    }

splice() 方法使用任意一列参数,但前两个参数始终引用索引号和要删除的元素个数。这就是为什么被覆盖的 splice() 方法仅对索引位置 2 或其以后的 args 数组元素执行类型检查。该代码中一个有趣的地方是, for 循环中的 splice() 调用看上去似乎是递归调用,但实际上并不是递归调用,这是由于 args 的类型是 Array 而非 TypedArray,也就是说, args.splice() 调用是对此方法超类版本的调用。在 for..in 循环结束后, args 数组中将只包含索引位置 2 或其以后位置中具有正确类型的值,并且 splice() 会调用其自身的超类版本,如下面的代码所示:

    AS3 override function splice(...args):* 
    { 
        if (args.length > 2) 
        { 
            for (var i:int=2; i< args.length; i++) 
            { 
                if (!(args[i] is dataType)) 
                { 
                    args.splice(i,1); 
                } 
            } 
        } 
        return (super.splice.apply(this, args)); 
    }

用于将元素添加到数组开头的 unshift() 方法也可以接受任意一列参数。被覆盖的 unshift() 方法使用的算法与 push() 方法使用的算法非常类似,如下面的示例代码所示:

    AS3 override function unshift(...args):uint 
    { 
        for (var i:* in args)  
        { 
            if (!(args[i] is dataType)) 
            { 
                args.splice(i,1); 
            } 
        } 
        return (super.unshift.apply(this, args)); 
    } 
}