擴充 Array 類別

Flash Player 9 以及更新的版本,Adobe AIR 1.0 以及更新的版本

Array 類別是少數並非 final 的核心類別之一,這表示您可以建立自己的 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 命名空間而非 public 特質,因為此範例會假設編譯器選項 -as3 已設定為 true ,而且編譯器選項 -es 已設定為 false 。這些都是 Adobe Flash Builder 和 AdobeFlashProfessional 的預設設定。

若您是偏好使用原型繼承的進階開發人員,可以對 TypedArray 類別做兩個小變更,使它在編譯器選項 -es 設定為 true 的情況下進行編譯。首先移除所有的 override 特質,並將 AS3 命名空間更換為 public 特質。其次,以 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 陳述式中, length 屬性的值是指定在 for 迴圈之後,因此 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 類別會覆寫 Array 類別的四種方法,而這四種方法都能新增元素到陣列中。在每種情況中,覆寫方法都會新增類型檢查,以防止新增資料類型不正確的元素。接著,每種方法都會呼叫其本身的父類別版本。

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)); 
    } 
}