Avancerade användningsområden

Historik över ActionScript OOP-stöd

Eftersom ActionScript 3.0 bygger på tidigare versioner av ActionScript bör du känna till hur ActionScript-objektmodellen har utvecklats. ActionScript började som en enkel skriptfunktion för tidiga versioner av Flash Professional. Programmerarna började efterhand bygga mera komplexa program med ActionScript. Som svar på deras behov har efterföljande versioner fått nya språkfunktioner som förenklar komplexa program.

ActionScript 1.0

ActionScript 1.0 är den version av språket som används i Flash Player 6 och tidigare versioner. Även i detta tidiga utvecklingsstadium var ActionScript-objektmodellen baserad på konceptet med objekt som fundamental datatyp. Ett ActionScript-objekt är en sammansatt datatyp med en grupp egenskaper. När objektmodellen beskrivs ingår i termen egenskaper allt som bifogats till ett objekt, till exempel variabler, funktioner och metoder.

Även om den första generationen ActionScript inte stöder definitionen av klasser med nyckelordet class, kan du definiera en klass med ett särskilt objekt som kallas prototypobjekt. I stället för att använda nyckelordet class för att skapa en abstrakt klassdefinition som du instansierar till konkreta objekt, som du gör i klassbaserade språk som Java och C++, använder prototypbaserade språk som ActionScript 1.0 ett befintligt objekt som modell (eller prototyp) för andra objekt. Medan objekt i ett klassbaserat språk kan peka på en klass som fungerar som mall, pekar objekt i ett prototypbaserat språk på ett annat objekt, prototypen, som fungerar som mall.

Om du vill skapa en klass i ActionScript 1.0 definierar du en konstruktorfunktion för denna klass. I ActionScript är funktioner verkliga objekt och inte bara abstrakta definitioner. Konstruktorfunktionen som du skapar fungerar som prototypobjekt för instanser av den här klassen. I följande kod skapas klassen Shape och definieras egenskapen visible som har värdet true som standard:

// base class 
function Shape() {} 
// Create a property named visible. 
Shape.prototype.visible = true;

Den här konstruktorfunktionen definierar klassen Shape som du kan instansiera med operatorn new:

myShape = new Shape();

På samma sätt som konstruktorfunktionsobjektet Shape() fungerar som prototyp för instanser av klassen Shape, kan det också fungera som prototyp för underklasser till Shape, d.v.s. andra klasser som utökar klassen Shape.

Att skapa en klass som är underklass till klassen Shape är en tvåstegsprocess. Först definierar du en konstruktorfunktion för klassen:

// child class 
function Circle(id, radius) 
{ 
this.id = id; 
this.radius = radius; 
}

Sedan använder du operatorn new för att deklarera att klassen Shape är prototyp för klassen Circle. Som standard används klassen Object som prototyp för alla klasser du skapar, vilket betyder att Circle.prototyp innehåller ett generiskt objekt (en instans av klassen Object). Om du vill ange att Circles prototyp är Shape och inte Object använder du följande kod för att ändra värdet för Circle.prototyp så att det innehåller ett Shape-objekt i stället för ett generiskt objekt:

// Make Circle a subclass of Shape. 
Circle.prototype = new Shape();

Klassen Shape och Circle har nu kopplats ihop i en arvsrelation som kallas prototypkedja. Bilden visar relationen i en prototypkedja:

Basklassen i slutet av alla prototypkedjor är klassen Object. Klassen Object innehåller den statiska egenskapen Object.prototype, som pekar på basprototypsobjektet för alla objekt som skapas i ActionScript 1.0. Nästa objekt i prototypkedjan är objektet Shape. Detta beror på att egenskapen Shape.prototype aldrig angavs explicit, och den innehåller fortfarande ett generiskt objekt (en instans av klassen Object). Den sista länken i kedjan är klassen Circle som är kopplad till sin prototyp, klassen Shape (egenskapen Circle.prototype innehåller ett Shape-objekt).

Om du skapar en instans av klassen Circle, som i följande exempel, ärver instansen prototypkedjan för klassen Circle:

// Create an instance of the Circle class. 
myCircle = new Circle();

Kom ihåg att exemplet också innehöll egenskapen visible som medlem i klassen Shape. I det här exemplet finns egenskapen visible inte som en del av objektet myCircle, utan bara som medlem i objektet Shape, och ändå returnerar följande kodrad true:

trace(myCircle.visible); // output: true

Körningsmiljön kan fastställa att objektet myCircle ärver egenskapen visible genom att gå upp i prototypkedjan. När den här koden körs genomsöks först egenskaperna för objektet myCircle efter egenskapen visible, men utan att hitta den. Därefter genomsöks objektet Circle.prototype, men fortfarande hittas inte egenskapen visible. Efterhand som sökningen rör sig uppåt i prototypkedjan hittas egenskapen visible, som definierats för objektet Shape.prototype, och värdet för egenskapen matas ut.

För enkelhets skull utelämnas många av de invecklade detaljerna kring prototypkedjan. Målet är i stället att ge tillräckligt mycket information för att ge en förståelse för objektmodellen i ActionScript 3.0.

ActionScript 2.0

ActionScript 2.0 innehåller nya nyckelord som class, extends, public och private, som gör att du kan definiera klasser på ett sätt som är bekant för alla som arbetar med klassbaserade språk som Java och C++. Du bör känna till att den underliggande arvsmekanismen inte ändrats mellan ActionScript 1.0 och ActionScript 2.0. I ActionScript 2.0 har bara infogats ny syntax för klassdefinition. Prototypkedjan fungerar på samma sätt i båda språkversionerna.

Med den nya syntaxen som introducerades i ActionScript 2.0 kan du definiera klasser på ett mera intuitivt sätt, vilket visas i följande utdrag:

// base class 
class Shape 
{ 
var visible:Boolean = true; 
}

Observera att ActionScript 2.0 också innehåller typanteckningar som ska användas för typkontroll vid kompilering. Du kan då deklarera att egenskapen visible i föregående exempel ska innehålla bara ett booleskt värde. Med det nya nyckelordet extends förenklas också processen att skapa en underklass. I följande exempel har tvåstegsprocessen som är nödvändig i ActionScript 1.0 utförts i ett steg med nyckelordet extends.

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

Konstruktorn deklareras nu som en del av klassdefinitionen, och klassegenskaperna id och radius måste också deklareras explicit.

ActionScript 2.0 har också stöd för definition av gränssnitt vilket gör att du ytterligare kan förfina dina objektorienterade program med formellt definierade protokoll för kommunikation mellan objekt.

Klassobjektet i ActionScript 3.0

Ett vanligt objektorienterat programmeringsparadigm, som vanligtvis associeras med Java och C++, använder klasser för att definiera olika typer av objekt. Programmeringsspråk som innehåller det här paradigmet tenderar att använda klasser för att skapa instanser av den datatyp som definieras av klassen. I ActionScript används klasser för båda dessa ändamål, men programmets rötter som ett prototypbaserat språk inbegriper en intressant egenskap. I ActionScript skapas för varje klassdefinition ett speciellt klassobjekt som tillåter delning av både beteende och tillstånd. För de flesta ActionScript-programmerare får denna skillnad inga praktiska kodningskonsekvenser. I ActionScript 3.0 kan du skapa sofistikerade objektorienterade ActionScript-program utan att behöva använda, eller ens förstå, dessa speciella klassobjekt.

I följande bild visas strukturen hos ett klassobjekt som representerar en enkel klass som heter A som definierats med programsatsen class A {}:

Varje rektangel i bilden representerar ett objekt. Varje objekt i bilden har ett nedsänkt A som anger att det hör till klass A. Klassobjektet (CA) innehåller referenser till ett antal andra viktiga objekt. Ett instans-traits-objekt (TA) lagrar instansegenskaperna som definierats i en klassdefinition. Ett klass-traits-objekt (TCA) representerar den interna typen av klassen och lagrar de statiska egenskaper som definierats av klassen (det nedsänkta tecknet C står för ”class”). Prototypobjektet (PA) avser alltid det klassobjekt som det ursprungligen kopplades till via egenskapen constructor.

Objektet traits

Objektet traits, som är nytt i ActionScript 3.0, har implementerats för att höja prestanda. I tidigare versioner av ActionScript kunde namnsökning vara ganska tidsödande eftersom Flash Player gick uppåt i prototypkedjan. I ActionScript 3.0 är namnsökningen mycket effektivare och mindre tidsödande eftersom ärvda egenskaper kopieras ned från superklasserna till underklassernas traits-objekt.

Objektet traits är inte direkt åtkomligt för programmeringskoden, men du märker att det finns genom att prestanda och minnesanvändning har förbättrats. Via objektet traits får AVM2 detaljerad information om en klass layout och innehåll. Med denna kunskap kan körningstiden med AVM2 förkortas eftersom direkta maskininstruktioner kan genereras till åtkomstegenskaperna och metoderna kan anropas direkt utan tidsödande namnsökning.

Tack vare objektet traits kan ett objekts minnesavtryck bli mycket mindre än ett liknande objekts avtryck i tidigare versioner av ActionScript. Om en klass är fast (d.v.s. inte har deklarerats dynamiskt) behöver en instans av klassen inte en hashtabell för dynamiskt infogade egenskaper och instansen kan innehålla lite mer än en pekare till traits-objekt och några fack för de fasta egenskaper som definierats i klassen. Därför kan ett objekt som kräver 100 byte minne i ActionScript 2.0 bara kräva 20 byte i ActionScript 3.0.

Obs! Objektet traits är en intern implementeringsdetalj och det finns ingen garanti för att det inte ändras eller försvinner i framtida versioner av ActionScript.

Objektet prototype

Varje objekt tillhörande klassen ActionScript har egenskapen prototype, som är en referens till klassens prototypobjekt. Prototypobjektet är ett arv från ActionScript som prototypbaserat språk. Mer information finns i Historik över ActionScript OOP-stöd.

Egenskapen prototype är skrivskyddad, vilket betyder att den inte kan ändras så att den pekar på andra objekt. Så är inte fallet med klassegenskapen prototype i tidigare versioner av ActionScript, där prototypen kan omtilldelas så att den pekar på en annan klass. Även om egenskapen prototype är skrivskyddad, är prototypobjektet som det refererar till inte skrivskyddat. Nya egenskaper kan alltså läggas till i prototypobjektet. Egenskaper som lagts till i prototypobjektet delas bland alla klassens instanser.

Prototypkedjan, som var den enda arvsmekanismen i tidigare versioner av ActionScript, fungerar bara som en andra roll i ActionScript 3.0. Den primära arvsmekanismen, arv av fasta egenskaper, hanteras internt av objektet traits. En fast egenskap är en variabel, eller metod, som definierats som en del av en klassdefinition. Arv av fasta egenskaper kallas också klassarv eftersom det är den arvsmekanism som associeras med nyckelord som class, extends och override.

Med prototypkedjan fås en alternativ arvsmekanism som är mera dynamisk än arv av fasta egenskaper. Du kan lägga till egenskaper i en klass prototypobjekt som en del av klassdefinitionen, men också vid körning via klassobjektets prototype-egenskap. Om du ställer in kompilatorn på strikt läge kanske du bara kan komma åt egenskaper som lagts till i prototypobjektet om du deklarerar en klass med nyckelordet dynamic.

Ett bra exempel på en klass med flera egenskaper bifogade till prototypobjektet är klassen Object. Klassen Objects metoder toString() och valueOf() är i själva verket funktioner som kopplats till egenskaper för klassens prototypobjekt. Nedanstående är ett exempel på hur deklarationen av dessa metoder i teorin kan se ut (den verkliga implementeringen ser en aning annorlunda ut på grund av implementeringsdetaljer):

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

Som tidigare nämnts kan du bifoga en egenskap till prototypobjektet för en klass utanför klassdefinitionen. Metoden toString() kan till exempel också definieras utanför definitionen av klassen Object:

Object.prototype.toString = function() 
{ 
    // statements 
};

Prototyparv kräver inte, i motsats till arv av fasta egenskaper, att du använder nyckelordet override om du vill omdefiniera en metod i en underklass. Om du till exempel vill omdefiniera metoden valueOf() i en underklass till klassen Object finns det tre alternativ. För det första kan du definiera metoden valueOf() på underklassens prototypobjekt inuti klassdefinitionen. I följande kod skapas en underklass av Object som heter Foo och omdefinieras metoden valueOf() på Foos prototypobjekt som en del av klassdefinitionen. Eftersom alla klasser ärver från Object behöver du inte använda nyckelordet extends.

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

För det andra kan du definiera metoden valueOf() på Foos prototypobjekt utanför klassdefinitionen, vilket visas i följande kod:

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

För det tredje kan du definiera en fast egendom som heter valueOf() som en del av klassen Foo. Denna teknik skiljer sig från de andra genom att den blandar arv av fast egenskap med prototyparv. Alla underklasser till Foo som vill omdefiniera valueOf() måste använda nyckelordet override. I följande kod visas valueOf() som definierats som en fast egenskap i Foo:

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

Namnutrymmet AS3

Eftersom det finns två separata arvsmekanismer, arv av fast egenskap och prototyparv, skapas en intressant kompatibilitetsutmaning med hänsyn till egenskaper och metoder i huvudklasserna. Kompatibilitet med språkdefinitionen ECMAScript som ActionScript baseras på kräver prototyparv, vilket betyder att egenskaper och metoder i en huvudklass definieras för prototypobjektet för denna klass. Kompatibilitet med ActionScript 3.0 kräver däremot arv av fast egenskap, vilket betyder att egenskaper och metoder i en huvudklass definieras i klassdefinitionen med nyckelorden const, var och function. Användningen av fasta egenskaper i stället för prototypversionerna kan dessutom ge rejäla höjningar av körningsprestanda.

I ActionScript 3.0 löses detta problem genom att både prototyparv och arv av fasta egenskaper används för huvudklasserna. Varje huvudklass innehåller två uppsättningar egenskaper och metoder. Den ena uppsättningen definieras för prototypobjektet för att ge kompatibilitet med ECMAScript-specifikationen och den andra uppsättningen definieras med fasta egenskaper och AS3-namnutrymmet för att ge kompatibilitet med ActionScript 3.0.

Med hjälp av AS3-namnutrymmet är det lätt att välja mellan de två uppsättningarna egenskaper och metoder. Om du inte använder AS3-namnutrymmet ärver en instans av en huvudklass de egenskaper och metoder som definierats på huvudklassens prototypobjekt. Om du bestämmer dig för att använda AS3-namnutrymmet ärver en instans av en huvudklass AS3-versionerna eftersom fasta egenskaper alltid prioriteras framför prototypegenskaper. När en fast egenskap är tillgänglig används den alltså alltid i stället för en prototypegenskap med samma namn.

Du kan selektivt använda AS3-namnutrymmesversionen för en egenskap eller metod genom att kvalificera den med AS3-namnutrymmet. I följande kod används AS3-versionen av metoden Array.pop():

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

Du kan också använda direktivet use namespace för att öppna AS3-namnutrymmet för alla definitioner i ett kodblock. I följande kod används direktivet use namespace för att öppna AS3-namnutrymmet för båda metoderna pop() och push():

use namespace AS3; 
 
var nums:Array = new Array(1, 2, 3); 
nums.pop(); 
nums.push(5); 
trace(nums) // output: 1,2,5

I ActionScript 3.0 finns också kompilatoralternativ för alla uppsättningar av egenskaper så att du kan tillämpa AS3-namnutrymmet på hela programmet. Kompilatoralternativet -as3 representerar AS3-namnutrymmet och kompilatoralternativet -es representerar alternativet för prototyparv (es står för ECMAScript). Om du vill öppna AS3-namnutrymmet för hela programmet anger du värdet true för kompilatoralternativet -as3 och värdet false för kompilatoralternativet -es. Om du vill använda prototypversionerna anger du omvända värden för kompilatoralternativen. Standardkompileringsinställningarna för Flash Builder och Flash Professional är -as3 = true och -es = false.

Om du tänker utöka någon av huvudklasserna och åsidosätta några metoder, måste du känna till hur AS3-namnutrymmet kan påverka deklarationen av en åsidosatt metod. Om du använder AS3-namnutrymmet måste alla metoder som åsidosätts av en huvudklassmetod också använda AS3-namnutrymmet tillsammans med attributet override. Om du inte använder AS3-namnutrymmet och vill definiera om en huvudklassmetod i en underklass, kan du inte använda AS3-namnutrymmet eller nyckelordet override.