進階主題

本節從扼要介紹 ActionScript 及 OOP 的歷史背景開始,然後接著探討 ActionScript 3.0 物件模型,以及此模型如何能讓新的 ActionScript Virtual Machine (AVM2) 執行速度大幅提升,比包含舊 ActionScript Virtual Machine (AVM1) 的舊版 Flash Player 快得多。

副主題

ActionScript 支援 OOP的歷史背景
ActionScript 3.0 類別物件
特性 (Traits) 物件
原型 (Prototype) 物件
AS3 命名空間

ActionScript 支援 OOP的歷史背景

由於 ActionScript 3.0 是建立在舊版 ActionScript 的基礎上,瞭解 ActionScript 物件模型的演進情況可能會有幫助。ActionScript 是從供舊版 Flash 編寫工具使用的簡單 Script 編寫機制開始;後來,程式設計人員開始用 ActionScript 建立複雜度不斷增加的應用程式。為了回應這些程式設計人員的需求,每一個後續版本都會有新增的語言功能,以便協助建立複雜的應用程式。

ActionScript 1.0

ActionScript 1.0 指的是用於 Flash Player 6 及更早版本中的語言版本。即使在這麼早期的開發階段,ActionScript 物件模型就根據以物件做為基礎資料類型的概念建立。ActionScript 物件是具有一組「屬性」的複合資料類型;討論物件模型時,「屬性」一詞包含附加至物件的一切項目,如變數、函數或方法。

雖然這第一代 ActionScript 並不支援使用 class 關鍵字的類別定義,但可以使用稱為原型 (Prototype) 物件的特殊物件定義類別。ActionScript 1.0 原型語言跟 Java 和 C++ 類別語言不同:後者兩種語言使用 class 關鍵字建立抽象類別定義,以便實體化成具體物件,而 ActionScript 1.0 則是使用現有物件做為其它物件的模型 (或原型)。類別語言中的物件可以指向類別做為其範本,而原型語言中的物件卻是指向其它物件 (即其原型) 做為其範本。

若要在 ActionScript 1.0 中建立類別,必須為該類別定義建構函數。在 ActionScript 中,函數實際上就是物件,而不只是抽象定義。您所建立的建構函數是做為該類別之實體的原型物件。下列程式碼會建立名為 Shape 的類別,並定義一個名為 visible 的屬性,依預設設定為 true

// 基底類別
function Shape() {}
// 建立名為 visible 的屬性。
Shape.prototype.visible = true;

這種建構函數會定義您可以用 new 運算子實體化的 Shape 類別,如下所示:

myShape = new Shape();

就像 Shape() 建構函數物件可以做為 Shape 類別的實體原型,它也可以做為 Shape 的子類別 (也就是其它擴充 Shape 類別的類別) 之原型。

建立 Shape 類別的子類別是兩步驟程序。首先,定義類別的建構函數以建立該類別,如下所示:

// 子類別
function Circle(id, radius)
{
    this.id = id;
    this.radius = radius;
}

第二步,使用 new 運算子,以宣告 Shape 類別為 Circle 類別的原型。依預設,您所建立的任何類別都會使用 Object 類別做為其原型,也就是說,Circle.prototype 目前包含一般物件 (Object 類別的實體)。若要指定 Circle 的原型為 Shape 而不是 Object,請使用下列程式碼變更 Circle.prototype 的值,讓它包含 Shape 物件,而不是包含一般物件:

// 讓 Circle 成為 Shape 的子類別。
Circle.prototype = new Shape();

Shape 類別和 Circle 類別現在會以繼承關係連結成一體,這關係一般稱為「原型鏈」。下圖可說明原型鏈中的關係:


原型鏈中的關係:Circle.prototype 繼承自 Shape.prototype,而後者繼承自 Object.prototype。

每一個原型鏈尾端的基底類別都是 Object 類別。Object 類別包含名為 Object.prototype 的靜態屬性,此屬性指向以 ActionScript 1.0 建立的所有物件的基底原型物件。範例原型鏈的下個物件是 Shape 物件。這是因為 Shape.prototype 屬性從未明確地設定,因此它仍然保存一般物件 (Object 類別的實體)。這個鏈中最後一個連結是 Circle 類別,它是連結至其原型 - Shape 類別 (Circle.prototype 原型保存了 Shape 物件)。

如果我們建立 Circle 類別的實體,就像下列範例中一樣,實體繼承 Circle 類別的原型鏈:

// 建立 Circle 類別的實體。
myCircle = new Circle();

還記得吧!我們建立名為 visible 做為 Shape 類別的成員。在我們的範例中,visible 屬性不是 myCircle 物件的一部分,而只是做為 Shape 類別的成員,然而下列程式碼行卻輸出 true

trace(myCircle.visible); // 輸出:true

Flash Player 只要順著原型鏈往上走,就能查明 myCircle 物件是繼承 visible 屬性。執行此程式碼時,Flash Player 會先搜尋 myCircle 物件的所有屬性,尋找名為 visible 的屬性,但找不到這個屬性;接著,Flash Player 就在 Circle.prototype 物件中尋找,但還是找不到名為 visible 的屬性;繼續再順著原型鏈往上走,Flash Player 終於找到在 Shape.prototype 物件上定義的 visible 屬性,並輸出該屬性的值。

由於想要盡量簡潔,本節內容會省略原型鏈的許多細節和錯綜複雜,而將重點改放在提供足夠資訊,以協助您瞭解 ActionScript 3.0 物件模型。

ActionScript 2.0

ActionScript 2.0 導入新的關鍵字,如 classextendspublicprivate,可讓您以使用 Java 和 C++ 等類別語言者所熟悉的方式來定義類別。請您務必瞭解,ActionScript 1.0 和 ActionScript 2.0 之間的基礎繼承機制並沒有改變。ActionScript 2.0 只是增添了定義類別的新語法。原型鏈在這兩版語言中的運作方式完全一樣。

由 ActionScript 2.0 導入的新語法,如下列摘錄所示,讓您能夠以許多程式設計人員都會認為是更直覺的方式來定義類別:

// 基底類別
class Shape
{
    var visible:Boolean = true;
}

請注意,ActionScript 2.0 也導入類型附註,以供用來進行編譯階段類型檢查,如此可讓您宣告,上一個範例中的 visible 屬性應該只包含 Boolean 值;新的 extends 關鍵字也簡化了建立子類別的程序。在下列範例中,ActionScript 1.0 中必須要有兩個步驟的程序,而使用 extends 關鍵字只需一個步驟就可完成:

// 子類別
class Circle extends Shape
{
    var id:Number;
    var radius:Number;
    function Circle(id, radius)
    {
        this.id = id;
        this.radius = radius;
     }
}

建構函式現在是宣告為類別定義的一部分,而類別屬性 idradius 都必須明確地進行宣告。

ActionScript 2.0 也新增介面定義的支援,可讓您利用正式為物件間通訊定義的通訊協定,更進一步修改您的物件導向程式。

ActionScript 3.0 類別物件

一個常見的物件導向程式設計範式通常是與 Java 和 C++ 相關聯,是使用類別來定義物件的類型。採用這種範式的程式設計語言也傾向於使用類別來建構類別所定義資料類型的實體。ActionScript 使用類別同時作這兩種用途,但因紮根於原型語言,它增添了一項有趣的特性。ActionScript 為每一個定義建立特殊的類別物件,而允許同時共用兩種行為和狀態;但是對許多 ActionScript 程式設計人員來說,這項區別沒有實際的程式含意。ActionScript 3.0 的設計方式能讓您建立更複雜的物件導向 ActionScript 應用程式,而不需要使用,甚至也不需要瞭解這些特殊類別物件。本節會為想要充分運用類別物件的進階程式設計人員深入探討這些議題。

下圖會顯示類別物件的結構,它代表名為 A 而用陳述式 class A {} 定義的簡單類別:


簡單類別的內部結構。CA 有標示為類型而指向 TCA 的箭號、標示為特性而指向 TA 的箭號,以及標示為原型和建構函式而指向 PA 的箭號。

圖中的每一個矩形都代表一個物件。圖中每一個物件都有下標字元 A,以代表屬於類別 A,類別物件 (CA) 包含其它重要物件的數目參考。實體特性物件 (TA) 會儲存在類別定義之內定義的實體屬性。類別特性物件 (TCA) 代表類別的內部類型,並儲存由類別定義的靜態屬性 (下標字元 C 代表「類別」)。原型物件 (PA) 永遠都是指透過 constructor 屬性進行附加的原先類別物件。

特性 (Traits) 物件

特性物件是 ActionScript 3.0 中的新增物件,實作時是以效能為考量。在舊版 ActionScript 中,名稱查閱程序可能會相當耗費時間,因為 Flash Player 會順著原型鏈查詢;在 ActionScript 3.0 中,名稱查閱會更有效率,所耗費時間較少,因為繼承的屬性是從父類別往下複製,一直到子類別的特性物件。

特性物件不能直接由程式設計人員編寫的程式碼存取,但可由效能改善及記憶體使用情形感覺到它的存在。特性物件提供 AVM2 有關類別版面及內容的詳細資訊。掌握了這些資訊之後,AVM2 就能大幅減少執行時間,因為它可以經常直接由機器產生指示以存取屬性,或是直接呼叫方法,而不需進行耗費時間的名稱查閱。

因為有特性物件,一個物件的記憶體使用量會比舊版 ActionScript 中類似物件減少相當多。例如,若是類別被密封 (也就是,類別不是動態宣告),類別的實體就不需要供動態加入屬性使用的雜湊表,而且所包含的只是特性物件的指標,加上一些類別中所定義固定屬性的空間位置而已。其結果就是,在 ActionScript 2.0 中需要 100 位元組記憶體的物件,在 ActionScript 3.0 中可能只需要稍大於 20 位元組的記憶體就夠了。

注意

 

特性物件是內部實作詳細資訊,並不保證在將來新版 ActionScript 中不會改變或甚至完全消失。

原型 (Prototype) 物件

每個 ActionScript 類別物件都有名為 prototype 的屬性,是類別之原型物件的參考,原型物件是舊版 ActionScript 原型語言根源留下的產物。如需詳細資訊,請參閱ActionScript 1.0

prototype 屬性是唯讀屬性,也就是說,不能進行修改,另外指向不同的物件。這與舊版 ActionScript 中的類別 prototype 屬性不同,在舊版中原型可以重新指定,以指向不同的類別。雖然 prototype 屬性是唯讀性質,但所參考原型物件並不是唯讀的,換句話說,新的屬性可以加入至原型物件中,加入原型物件的屬性可以供類別中所有實體共用。

在舊版 ActionScript 中為唯一繼承機制的原型鏈,在 ActionScript 3.0 僅扮演次要角色。主要的繼承機制:固定的屬性繼承,則是由特性物件在內部進行處理。固定的屬性是定義為類別定義一部分的變數或方法。固定的屬性繼承也稱為類別繼承,因為它是與關鍵字 classextendsoverride 等相關聯的繼承機制。

原型鏈提供另外一種形式的繼承機制,比固定的屬性繼承更強,更靈活;不但可以將屬性做為類別定義的一部分加入至類別的原型物件,也可以在執行階段透過類別物件的 prototype 屬性加入。但請注意,如果將編譯器設定為嚴謹模式,可能就無法存取加入至原型物件的屬性,除非以 dynamic 關鍵字進行宣告。

Object 類別是有某些屬性附加至原型物件之類別的好範例。Object 類別的 toString()valueOf() 方法其實都是指定給 Object 類別之原型物件屬性的函數。下列範例示範這些方法的宣告在理論上的情況 (實際實作會因為實作細節的關係而稍微有些差異):

public dynamic class Object
{
    prototype.toString = function()
    {
        // 陳述式
    };
    prototype.valueOf = function()
    {
        // 陳述式
    };
}

上文已經討論過,您可以在類別定義之外,將屬性附加至類別的原型物件。例如,toString() 方法也可以在 Object 類別定義之外定義,如下所示:

Object.prototype.toString = function()
{
    // 陳述式
};

但是原型繼承與固定的屬性繼承不同,若要在子類別中重新定義方法,並不需要 override 關鍵字。例如,若要在 Object 類別的子類別中重新定義 valueOf() 方法,您有三種選擇:第一,您可以在類別定義中的子類別之原型物件上定義 valueOf() 方法。下列程式碼會建立名為 Foo 的 Object 之子類別,然後在 Foo 的原型物件上重新定義 valueOf() 方法,做為類別定義的一部分。由於每一個物件都是繼承自 Object,不需要使用 extends 關鍵字。

dynamic class Foo
{
    prototype.valueOf = function()
    {
        return "Instance of Foo";
    };
}

第二,您可以在類別定義之外,於 Foo 的原型物件上定義 valueOf() 方法,如下列程式碼所示:

Foo.prototype.valueOf = function()
{
    return "Instance of Foo";
};

第三,您可以定義名為 valueOf() 的固定屬性做為 Foo 類別的一部分。這種技巧與其它方法都不同,它混合了固定的屬性繼承與原型繼承。任何要重新定義 valueOf() 的 Foo 子類別都必須使用 override 關鍵字。下列程式碼示範將 valueOf() 定義為 Foo 中的固定屬性:

class Foo
{
    function valueOf():String
    {
        return "Instance of Foo";
    }
}

AS3 命名空間

因為有兩種不同的繼承機制 (固定的屬性繼承和原型繼承) 存在,就產生在核心類別的屬性及方法方面的相容性挑戰。與 ECMAScript 第 4 版草稿語言規格的相容性需要使用原型繼承,也就是說,核心類別的屬性和方法都是在該類別的原型物件上定義。另一方面,與 Flash Player API 的相容性則需要使用固定的屬性繼承,也就是說,核心類別的屬性和方法是在類別定義中,使用 constvarfunction 關鍵字定義;而且使用固定的屬性而不使用原型版本,可能會讓執行階段效能大幅提升。

ActionScript 3.0 是透過同時使用核心類別的原型繼承和固定屬性繼承,解決這個問題。每一個核心類別都包含兩組屬性和方法:一組是在原型物件上為 ECMAScript 規格之相容性而定義,另一組則是以固定的屬性和 AS3 命名空間,為 Flash Player API 之相容性而定義。

AS3 命名空間提供在兩組屬性和方法之間選擇的便利機制。若不使用 AS3 命名空間,核心類別的實體會繼承在核心類別之原型物件上定義的屬性和方法;若決定使用 AS3 命名空間,則核心類別的實體會繼承 AS3 版,因為固定的屬性總是比原型屬性優先繼承,換句話說,只要可以使用固定的屬性,一定會使用此屬性,而不使用名稱完全相同的原型屬性。

您可以透過用 AS3 命名空間加以限定,選擇性地使用 AS3 命名空間版屬性或方法。例如,下列程式碼會使用 AS3 版 Array.pop() 方法:

var nums:Array = new Array(1, 2, 3);
nums.AS3::pop();
trace(nums); // 輸出:1,2

另外,您也可以使用 use namespace 指令,開啟在程式碼區塊之內所有定義的 AS3 命名空間。例如,下列程式碼會使用 use namespace 指令,同時開啟 pop()push() 方法的 AS3 命名空間:

use namespace AS3;

var nums:Array = new Array(1, 2, 3);
nums.pop();
nums.push(5);
trace(nums) // 輸出:1,2,5

ActionScript 3.0 也為各組屬性提供編譯器選項,以便讓您將 AS3 命名空間套用至整個程式。-as3 編譯器選項代表 AS3 命名空間,而 -es 編譯器選項則代表原型繼承選項 (es 代表 ECMAScript)。若要為整個程式開啟 AS3 命名空間,請將 -as3 編譯器選項設定為 true,而將 -es 編譯器選項設定為 false;若要使用原型版,請將編譯器選項設定為相反的值。Adobe Flex Builder 2 和 Adobe Flash CS3 Professional 的預設編譯器設定是 -as3 = true-es = false

若計劃擴充任何核心類別及覆寫任何方法,應該要瞭解 AS3 命名空間會如何影響您必須宣告被覆寫方法的方式。若要使用 AS3 命名空間,核心類別的任何方法覆寫也必須使用 AS3 命名空間,以及 override 特質;若不是使用 AS3 命名空間,而要在子類別中重新定義核心類別方法,就不應該使用 AS3 命名空間或 override 關鍵字。


Flash CS3

 

有新的意見加入至這個頁面時,傳送電子郵件給我 | 意見報告

目前頁面: http://livedocs.adobe.com/flash/9.0_tw/main/00000069.html