The common language of Flex 2 applications is ActionScript 3.0. You can't use any older versions of ActionScript, nor is it possible to create a functional application without at least some ActionScript 3.0. Additionally, we will soon see that MXML and AS3 are fundamentally related.
ActionScript's third incarnation is a mature language, as well as being substantially different from ActionScript 2. This chapter will rapidly cover all the fundamental aspects of ActionScript 3.0, but is by no means a full course. For more instruction, there is a list of useful further reading at the bottom of this Short Cut.
Readers who understand ActionScript 2 should be comfortable transitioning to ActionScript 3.0. The following chapter specifically addresses new concepts since AS2, so read on!
Readers versed in C# or Java should find themselves at home with AS3 in short time, but should note a few differences in syntax and features. In any case, this chapter assumes, at minimum, basic knowledge of object-oriented programming.
ActionScript is an ECMAScript language; ActionScript 3.0 implements the draft of ECMAScript (ECMA-262) Edition 4. This means two things: one, its true name is ridiculous and you don't have to remember it, and two, it is a draft, so there may be incremental changes in the future. Ecma International is an international standards organization that oversees ECMAScript and other technological standards. JavaScript is another implementation of ECMAScript, so ActionScript 3.0 will more than likely be identical to JavaScript when browsers implement Edition 4 of ECMAScript!
ActionScript 3.0 is a true object-oriented language, in that all types, including base types, are classes. Even a literal like 5 may have Number methods called directly on it. AS3 supports single inheritance: each class can extend only one other class.
The prototypal nature of earlier editions of ECMAScript is being slowly paved over in favor classical usage. Some parts of AS3 allow you to use it as a prototypal language, but this ability is rarely used and is not covered here. The virtual machine is tooled to work best when you program classically.
AS3 is also a functional language. Functions are objects—first class citizens—and can be stored and passed around. Lambda (or anonymous) functions are supported. Furthermore, the Array class acts as a list and supports many common list operations from functional programming.
ActionScript 3.0 supports both dynamically and statically typed
variables. Dynamically typed, or "untyped" variables are able to change
their type at runtime, and can use type inference to mutate type based
on the value you assign to them. AS3 introduces the type *
to denote an untyped variable. The
compiler type-checks all typed variables and sealed classes, so using
statically typed variables helps the compiler ensure the correctness of
your programs. Classes are by default sealed: you cannot add properties
or methods to classes at runtime. However, dynamic classes remain
possible by using the dynamic
keyword.
ActionScript 3.0 has a native event model. Unlike previous versions of ActionScript in which you had to choose an event framework, and you might see several different event handling schemes at work at the same time, AS3 provides a flexible multi-phase event model. The model is based on DOM Level 3, a W3C standard.
Runtime error handling is made easier by AS3's greatly improved use of exceptions. Many errors that were silent in previous versions of ActionScript now throw exceptions, easing debugging and allowing for more comprehensive fallback strategies.
Behind much of the power of ActionScript 3.0 is a completely new virtual machine, the ActionScript Virtual Machine 2 (AVM2).
The virtual machine is what enables one SWF to run on a Mac, a PC, Linux, or a Pocket PC. AS3 code is compiled into bytecode, which are low-level instructions. The virtual machine inside Flash Player runs this bytecode. Classically, source code has been compiled into instructions for your CPU, but the result is that that program will run only on computers with the same CPU. Many modern languages have taken to using virtual machines to execute the same bytecodes, such as Java's JRE and .Net's CLR. Because your machine has to run a virtual machine inside of it, translating the instructions for the VM into instructions that your CPU can run, bytecode-interpreted languages can be slower than native programs, but they gain interoperability, safety, and flexibility.
Because the Flash Player must continue to be able to play older SWFs, Flash Player 9 actually contains two whole interpreters in it: AVM1 and AVM2. Whenever you use any ActionScript 3.0 in a SWF, it will run inside AVM2: this is the case for all Flex 2 applications. The AVM2 is new from the ground up, created with performance in mind, and its speed and stability make Flex 2 possible.
AVM2, and by association AS3, handles classes very differently
from previous versions of ActionScript. ActionScript classes began as
prototypal objects: in ActionScript 1 and in JavaScript, you can create
"classes" by adding methods and variables to a prototype object that
exists in memory. Inheritance was handled by copying the superclass's
prototype and adding methods and properties, thus extending it and
creating a prototype chain. In ActionScript 2, you could write classes
in a familiar structure with a class
block, but it was syntactic sugar: the ActionScript 2 compiler just
converted these to prototype objects for you. Prototypal classes,
however, have some glaring problems. Classes can be modified at runtime.
Methods and variables can be referred to without being declared, or
conversely can be overwritten at runtime. Perhaps most importantly,
every method or property requested from a class instance would start the
AVM on a wild hunt for that resource, looking in a hashtable in the
object, then its prototype, its superclass's prototype, all the way up
the inheritance chain!
The new virtual machine strikes a balance, allowing you to use the prototype object, but also flattening your classes. In the AVM2, all classes are stored in memory as a word-aligned structure, which means two things: class instances take up far less memory, and that looking up methods and properties is a direct operation. I have to mention all this because of one important implication. Using statically typed variables doesn't just check your work—it optimizes your program.
The AVM2 also gets a huge speed boost by using just-in-time compilation (JIT). Interpreting bytecode can be very slow compared to running instructions directly on your CPU. Just-in-time compilation will profile your application as it runs, discovering sections of code that run frequently, and further compile those into native code, all while your application is running. JIT, like compiler optimization, is a greatly behind-the-scenes process that you unfortunately can't play with, but its effects should have a terrific impact. For example, Java's improvements in JIT over the years have completely transformed an initially negative perception of its speed.
The AVM2 provides a garbage collection facility, so AS3 is a fully
memory-managed language. When references pass out of scope and no
(strong) references to them remain, they are later freed from memory.
Like Java, new
and delete
are the primary means to manage your
application's memory. However, the delete keyword is very limited in AS3
and setting references to null to allow objects to be collected is more
common. ActionScript 3.0 lets you write constructors, but no
destructors. The AVM2 uses a mark-and-sweep algorithm to perform garbage
collection.
A welcome new feature to the core AS3 language is the inclusion of XML as a native data type with literals and intuitive operators. This feature is called ECMAScript for XML (E4X), and is another Ecma International standard with the less memorable full name ECMA-357. Not only does this make XML faster and easier to use than before, but it also makes XML a viable type for internally storing and manipulating information.
The new XML
class is
accompanied by a very useful class named XMLList
. An XMLList relaxes one key
requirement of well-formed XML: the single root node. It also is the
typical return type for any filtering done using E4X methods and
contains useful methods to manipulate its contents. You will find using
an XMLList to store arbitrary chunks of XML data extremely convenient.
It is also worth noting that the XML class from ActionScript 2 still
exists as the class XMLDocument
,
making it easier to port legacy code.
Below is an example that just begins to show the things you can do with E4X.
//Just type out XML inline! No need for the <?xml version="1.0"?> prolog. var roster:XML = <root> <person name="Ezra" skill="flash"/> <person name="Joey" skill="flex"/> <person name="Danny" skill="flash"/> <person name="Mims" skill="flex"> <title>Minister of moustaches.</title> </person> <person name="Geoff" skill="javascript"/> <person name="Paul" skill="flash"/> <person name="Robert" skill="flash"/> <person name="Roger" skill="flex"> <title>Your humble author.</title> </person> </root>; //You can be part of the team now! (Add a new child) roster.appendChild(<person name="DearReader" skill="learning"/>); //Who's first in line? (Find the name attribute of the first person tag) trace(roster.person[0].@name); //Ezra //Find out what Roger's supposed to be doing here. //(The contents of the title node in the person tag whose name attribute is Roger) trace(roster.person.(@name == "Roger").title); //Your humble author. //Let's see how many Flexperts there are. //(The length of the XMLList made of person tags whose skill attribute is flex) trace(roster.person.(@skill == "flex").length()); //3 //You can save this line for the end of the book. //(Store a reference to the person tag with a name attribute of DearReader) //(Set the skill attribute on that person tag) var you:XMLList = roster.person.(@name == "DearReader"); you.@skill = "flex"; //New Flexpert Appeared! //(The length of the XMLList made of person tags whose skill attribute is flex) trace(roster.person.(@skill == "flex").length()); //4
This example is a small part of what's possible with E4X. You can
build complex searches by constructing a dot-separated query path. You
can select children by inserting the name of the child node into a dot
path (roster.person.title
), and you
can positionally select nodes with array-access-style bracket notation
(roster.person[0]
). You can navigate
into attributes with the at (@
)
symbol, and you can select nodes which match arbitrary conditions (not
just equality) within your path. The XMLList class provides more axis
selectors than shown in this example, paralleling the abilities of
XPath.
As you work with XML data in Flex 2, you can check the documentation and resources listed at the end of this Short Cut to learn more about E4X.
Another extremely useful and long-awaited language feature in AS3
is regular expressions. These objects make intricate string parsing a
snap. There are a plethora of books and articles on this topic such as
Mastering Regular Expressions, 2nd
Edition by Jeffrey Friedl (O'Reilly). AS3 supports regular
expressions as both literals—denoted by an expression surrounded by
backslashes (/hello/
)—and as
instances of the class RegExp
.
Besides being able to run RegExp objects on Strings by passing them as
arguments to RegExp instance methods, several String methods have been
upgraded to accept regular expressions as arguments. Here is an example
of how regular expressions might be used in ActionScript 3.0.
var str:String = "Rub a dub dub, three men in a tub. And who do you think they be?"; //look in the string for anything with a letter followed by "ub" str.match(/wub/g); //Rub,dub,dub,tub //make a regular expression which matches "dub dub" var re:RegExp = new RegExp("dub dub"); //test the string for existence of the pattern re.test(str); //true //replace the pattern with our text str.replace(re, "glub glub"); //Rub a glub glub, three men in a tub... //look for all 3 letter words //note that replace() returned the string but did not destructively modify it str.match(/w{3}/g); //Rub,dub,dub,men,tub,And,who,you //find the position of the first "a" character str.search(/a/); //4
Note that regular expression literals in AS3 are only used to
declare regular expressions, not to execute them, so you won't see
constructs like m/fnord/
(which would
search for the pattern "fnord" in many programming languages).
Flash developers, rejoice! The Flash Player API for managing visual elements (like MovieClip) has been completely redone. Whether or not you will be using Flash 9, it's important to understand this because Flex 2 uses the same paradigm. The new model is based on a display list.
Each visual element that may have children has a display list. You can think of it as a list because that's how you manage it, and because that's how depths are assigned. Only elements added to a display list get rendered when necessary. The list is rendered from the back to the front, and things on the front of the list are rendered on top of those in the back of the list.
In addition to how they are managed, the classes that represent
visual elements themselves have been organized and built out. In SWFs
published for Flash Player 9, everything visible on screen is in a
display list and accessible to code, even static text fields and simple
shapes. All display objects extend the abstract base class DisplayObject
, and objects that can themselves
contain other display objects also extend DisplayObjectContainer
. Following are the most
notable display objects that are available in AS3.
The MovieClip
class is
very similar to its counterpart from previous versions of
ActionScript: a visual element with an independent timeline that
can contain any other display objects. Note that all of the
properties that were once prefixed with an underscore, such as
_alpha
and _rotation
, have dropped the underscore
to become alpha
and rotation
. Note also that the class has
moved from the top level into the flash.display
package.
Like a MovieClip, a Sprite
can contain any kind of display
object, and can be transformed with the same properties. Sprites,
however, don't have timelines, and therefore are more efficient,
especially in large numbers. For many developers, Sprites will
handily replace MovieClips as the default building block.
Loader
s are used to
contain content that is loaded at runtime, both SWFs and graphics.
They replace both the MovieClipLoader
class and the loadMovie()
method from previous
versions of ActionScript.
These and the other display objects are essential for applications using the Flash Player 9 display list. While using Flex 2, you will have access to the Flash Player API and these classes, but keep in mind you will most likely use Flex 2 components instead of these classes.
The ways you can manipulate display objects are completely different from previous versions of ActionScript as well. Forget all you know about createEmptyMovieClip, attachMovie, and swapDepths, and concentrate on display objects and display lists:
var box:Sprite = new Sprite(); addChild(box); var jack:Sprite = new Sprite(); box.addChild(jack);
The first line creates a new empty Sprite. The second line will add the box to our display list, making it visible. Next, we create another new Sprite, this time a jack, and put the jack in the box.
Assets are handled as subclasses. If you actually had an image associated with the jack, you could name it Jack, and Jack would be a subclass of Sprite. Now the Jack class is just a special kind of Sprite that has a particular appearance. This idea replaces linkage names and can be found in both Flex 2 and Flash 9.
Instead of using the _visible
property from earlier versions of Flash, which often left the invisible
clip taking up resources every frame, simply remove it from the display
list that contains it. The display object is still there for your use
(as long as you keep a reference to it), but no time will be spent
rendering it or running its timeline.
box.removeChild(jack); royalFlush.addChild(jack);
You can even re-parent objects, moving them into completely different containers. Here we take jack out of the box and instead use it to complete royalFlush. You win!
ActionScript 3.0 adds a few core types and continues to support all of the ones present in AS2. It is worth mentioning again that AS3 is a true object-oriented language and all of these types are classes. Core types inherited from previous versions of ActionScript include:
A double-precision floating point number.
A representation of a specific point in time.
A string, much like Java's String. There is no type for a single character.
A Boolean value, true or false.
An untyped (it can contain objects of mixed type) auto-sizing list.
Anonymous object and the root class for all class hierarchies in AS3. Can add properties at runtime, making it useful as a dictionary that supports string keys.
A function.
AS3 introduces a few new core types:
A 32-bit signed (two's complement) integer
A 32-bit unsigned integer
XML data
A runtime representation of a class. Stores the class's
static properties and methods. Class references can be used with
the new
operator to create
instances of the class.
A representation of a namespace.
The base type for all exceptions.
ActionScript 3.0 provides more granularity in setting the visibility of properties, methods and classes than previous versions of ActionScript. In addition, the meaning of some visibility modifiers has changed since ActionScript 2.
Used to declare classes, properties, methods, and namespaces that are accessible to any caller.
Used only inside class definitions to declare properties, constants, methods, and namespaces that are available to the declaring class and any of its subclasses.
Used within class definitions to declare properties, constants, methods, and namespaces that are available only within the declaring class. More strict than its meaning in ActionScript 2.
Used to declare classes, properties, methods, and namespaces that are accessible within the package they are declared in.
If you omit a visibility attribute on any declaration inside a
class definition, AS3 will implicitly assign it the internal
attribute.
Furthermore, AS3 enables you to create and use custom namespaces.
Classes, properties, and methods declared in a custom namespace are
visible to other code in the same namespace, code which has opened the
namespace by using the use namespace
directive, or by using the name qualifier operator (::
) to specify the namespace the property is
found in. You can use any valid identifier name for your namespace, and
you may also bind it to a URI (Uniform Resource Identifier). When you
declare a class, property, or method in a custom namespace you may not
also use a predefined namespace like public
or private
.
ActionScript 3.0 provides you with a set of tools to manipulate
types safely and intelligently. The compiler type-checks your program
and can catch many errors at compile-time when you use typed variables.
The rules that are enforced are fairly straightforward. All typed
parameters of a function call or subjects of an operator must be passed
compatible types. In assignments, a variable (the left hand side) and
the expression or value you assign to it (the right hand side) must be
of compatible types. And an expression or value returned with the
return
keyword must match the return
type of the function.
Furthermore, the AVM2 stores type information with all variables
that are declared with a type. In previous versions of ActionScript, the
compiler would check types at compile time, but all objects in the AVM1
were dynamically typed, so that you could mutate the type of variables
at runtime by bypassing the compiler checks, for instance by referencing
the variable through a lookup. The AVM2 can enforce type safety at
runtime as well, and incompatible type assignments throw a TypeError
exception. This is good news,
because incompatible assignments that might otherwise silently
fail—causing later errors and forcing you to investigate—are now
immediately identifiable.
You may investigate the type of a value with the is
operator. The is
operator replaces the instanceof
operator from ActionScript 2 and
provides the same functionality as Java's instanceof
operator. The expression:
a is B
where a
is an object of type
A
and B
is a type will return true if B
can be found by following the type hierarchy
of A
up, that is, towards Object
. For example, a is B
if:
A
is the same class as
B
A
extends B
A
implements B
A combination of the above, e.g. A
's superclass's superclass implements
B
Earlier I mentioned the idea of compatible types. A type is
compatible with all types that are in the same type hierarchy but are
more general. For instance, Dessert
is more general than Cupcake
, so you
could refer to a particular Cupcake
as a Dessert
with no problem.
ActionScript 3.0 lets you assign values to variables of more general
types without hassle. So you could easily do
public function eatDessert(somethingTasty:Dessert):void { trace("yum.. that's a good " + somethingTasty.toString()); } var chocolateCupcake:Cupcake = Magnolia.getCupcake("chocolate"); eatDessert(chocolateCupcake); //chocolateCupcake is treated as a Dessert trace(chocolateCupcake is Dessert); //true
This example passes a Cupcake
to a function that expects a Dessert
,
but the types are compatible so the code is valid. Another situation
where types are compatible is when AS3 knows how to convert one type
into another. In certain situations, AS3 automatically performs this
type coercion. For instance:
var coerce:String = "Party like it's " + 2099;
implicitly coerces the integer 2099
into a String
which the +
operator expects in a String
context.
It is a desirable object-oriented programming practice to program
to interfaces and not implementations. So if you type all of your
variables as IEdible
, how will you
get a Cupcake
back when you need one?
Occasionally it is necessary to convert types toward the more specific,
and AS3 allows you to do this with two kinds of explicit type
conversion: a cast and a conditional cast.
Casting forces a variable of one type to be another type. To capture the result of this operation, you should assign it to a new variable of the desired type, for instance,
var cupcake:Cupcake = Cupcake(somethingTasty);
Java and C developers, take note that the parentheses appear
around the expression rather than the type. If this type conversion is
impossible, that is, if the object you are casting is not actually the
type you request or compatible with the type you request, a TypeError
is thrown. In this example, your
somethingTasty
, which is currently
typed as a Dessert
, must have
originally been a Cupcake
.
var cupcake:Dessert = new Cupcake(); //using a Cupcake as a Dessert, OK var baklava:Dessert = new Baklava(); //using a Baklava as a Dessert, OK Cupcake(cupcake).addSprinkles(); //OK because it started life as a Cupcake Cupcake(baklava).addSprinkles(); //NOT OK! Baklava is not compatible with Cupcake
The as
operator performs a
conditional cast. If the requested type conversion is not possible,
instead of throwing a TypeError
, the
expression merely returns null. This is similar to a dynamic_cast
in C++ and the identical operator
in C#. This is also more or less the behavior of casts in ActionScript
2. Unless you can check the result of a conditional cast, or you have
previously verified that the types are compatible, you should use a
normal cast instead. This way you can catch potential problems as
exceptions. However, the as
operator's special properties can make it useful to create creatively
concise code, such as this example:
var cookie:Cookie; for each (var dessert:Dessert in desserts) { //use the as operator to frost all cookies in a dessert list //returns null if the cast fails, skipping the if block if (cookie = dessert as Cookie) { cookie.frost(); } }
It is also worth mentioning here how ActionScript 3.0 deals with
initial values. If a variable does not have a value assigned to it, but
has a specific type, its default value depends on its type. For example,
an unassigned int
has the value
0
, an unassigned Number
has the value NaN
, and all complex types have a default
value of null
. Core types that have
default values can't be assigned null
or undefined
. The value undefined
still exists, but only represents an
unassigned, untyped variable. In general, AS2 developers coming to AS3
can forget about the confusing differences between undefined
and null
, and stick to null
.
Packages are a way to organize code and avoid name collisions. In
ActionScript 2, you would place class files in a folder structure that
defined their fully qualified class name. For example, the class
com.partlyhuman.Banana
would be
defined in the file com/partlyhuman/Banana.as
. In ActionScript
3.0, packages are more than file paths. You must use the package
block to declare packages, and only
code found inside package blocks is visible outside of a file. Packages
are also used to control access, as you have seen with internal
.
Classes are defined in a class
block. To make your class visible outside the file it is written in, you
must place it in the appropriate package block and declare it as
public:
//in file com/partlyhuman/Banana.as package com.partlyhuman { public class Banana { } }
Packages may contain not just classes, but functions, variables, namespaces, and even statements. However, a maximum of one item per file is allowed to be visible to the outside, and the file's name must mirror this item. Items declared inside a package block are allowed only two access specifiers: public and internal.
Typically, you will write files with a package block containing one public class or function. One exception is a common implementation of the Singleton pattern, which provides global access to a single instance and prevents instantiation of more than one instance. Because AS3 does not permit private constructors, the pattern requires an object to be passed to the constructor whose type is visible only inside the same file, so that code external to this class won't be able to provide the appropriate argument to the constructor, making it as good as private:
package com.partlyhuman { public class SingletonDemonstration { private static var instance:SingletonDemonstration; public function SingletonDemonstration(enforcer:SingletonEnforcer) { //initialization code } public static function getInstance():SingletonDemonstration { if (instance == null) { instance = new SingletonDemonstration(new SingletonEnforcer()); } return instance; } } } class SingletonEnforcer {}
ActionScript 3.0 supports similar inheritance patterns to Java. Classes may extend one class and implement multiple interfaces.
public interface IPoppable { function pop():void } public interface IInflatable { function inflate():void } public class Balloon implements IPoppable, IInflatable { public function Balloon() { //initialize the balloon } public function pop():void { //you must implement methods from all interfaces //methods found in interfaces are always public } public function inflate():void { //inflate } } public final class Blimp extends Balloon { public function Blimp() { //call the superclass constructor super(); //do more fun stuff } public function fly():void { //a blimp is like a balloon that can fly... or something. } }
Here, a Balloon
class is
created that supports the operations defined in the interfaces IPoppable
and IInflatable
. These interfaces may only define
methods, and you don't need to specify the visibility of the methods:
they must all be implemented as public.
Abstract classes, or virtual or abstract methods, are not part of AS3. You can enforce abstractness at runtime, however, by throwing errors in your abstract class's constructor or your abstract method.
The above example also shows an example of a final class. Final
classes may not be extended; they are the final class in their class
hierarchy. This prevents people using your code from taking your hard
work on the Blimp
and using it for
evil, creating MissileBlimps
and
LaserBlimps
. In reality, this might
be used for classes that are provided to use but should not be modified,
for example flash.system.Security
.
Classes may also be declared dynamic
. When this keyword is not used,
classes are sealed: their methods and properties are written in stone
and may not be modified at runtime. With dynamic
, you can access and create properties
and methods on instances of that class at runtime. This can be useful to
create simple wrappers of data, for example, storing arbitrary data in
Object instances. Dynamic classes cannot benefit from some of the
optimizations of the AVM2, so use them only when appropriate.
Let's add a little bit more to that Blimp
class:
public final class Blimp extends Balloon { private const TAKEOFF_PRESSURE:Number = 10000; private var currentPressure:Number; public function Blimp() { super(); currentPressure = 100; } override public function pop():void { //abandon ship! } override public function inflate():void { //inflate the same way as a balloon, then add our own behavior super.inflate(); if (currentPressure >= TAKEOFF_PRESSURE) fly(); } public function fly():void { //go, go, majestic blimp! } }
There are a few new features introduced in AS3 which are
demonstrated here. ActionScript 3.0 includes constants, variables whose
values may not be changed. The compiler will stop you if you try to make
a second assignment to a constant, or from outside the class definition.
The Blimp's inflate()
method uses the
constant TAKEOFF_PRESSURE
to check if
it's ready to take off. Constants are declared with the keyword const
in place of var
.
You might also note that the method pop()
is marked override
as well as public
. ActionScript 3.0 allows you to write
methods that also exist on the superclass, but you must assure the
compiler that you are intentionally overwriting this method with the
override
keyword. The overriding
method may also call the original implementation by calling the same
method on the super
object, a
reference to the class's superclass. Also, the signature of the
overriding method must completely match the signature of the overridden
method. ActionScript 3.0 supports only one method with the same name, so
this means you can't do overloading, where a method can perform
differing operations based on its input. However, you can always use a
variable argument list (as discussed below) to achieve this.
Static variables, or class variables, are allowed in ActionScript
3.0, identically to AS2. Static variables are declared with the static
keyword. These variables belong to the
class itself, so they will be the same across instances of the same
class. This could be used in our Blimp
to store properties that should be the
same across all blimps, like the number of wheels on the landing
gear.
ActionScript 3.0 supports implicit, as well as explicit,
accessors. Explicit accessors are normal methods that retrieve or set
values; for example, if the Blimp has setAltitude()
and getAltitude()
methods which manipulate the
Blimp's internal altitude. Accessors provide an alternative to declaring
public instance variables, and give your class the control to perform
certain checks against the set value, or additional operations when
those values are set. This is a good practice that you should follow to
maintain encapsulation. Implicit accessors let client code maintain the
illusion of setting a property directly on your class, while still
allowing you to intercept control. Instead of:
public function setAltitude(newAltitude:Number):void {...} public function getAltitude():Number {...} myBlimp.setAltitude(10);
you could use implicit accessors by writing:
public function set altitude(newAltitude:Number):void {...} public function get altitude():Number {...} myBlimp.altitude = 10;
Finally, ActionScript 3.0 supports method closures or bound methods. A closure is a function that can refer to variables in its surrounding block, regardless of where it is called from. It's like packaging a function with its own scope. Many AS2 developers may be familiar with the code
var closure:Function = Delegate.create(this, doFoo);
which creates a closure. Any time you run closure()
, doFoo()
executes in the original scope you
called Delegate.create()
from. In
AS3, all methods defined in a class are bound to that class without any
extra work, so even if you execute those methods from an event handler
or from a reference to the method passed as a Function, the methods can
access their owner class's methods and properties.
The ActionScript 3.0 compiler enforces that your function calls match the function's signatures. Generally, parameters must appear in the correct order, with compatible types, and without any parameters missing. However, ActionScript 3.0 adds some tools to relax these requirements and to add convenience as well as flexibility.
Default values are a new addition to ActionScript from the C++ world. You can make some parameters to your function optional by declaring default values for these arguments right in the argument list. For example, you might have a complex operation with lots of options:
function makeDagwood(layers:int, meat:String, cheese:String, bread:String, mayo:Boolean, mustard:Boolean, lettuce:Boolean):Sandwich {...}
While you want users of your code to have full control of their sandwich, you want to keep their code concise and specify some reasonable defaults:
function makeDagwood(layers:int, meat:String, cheese:String, bread:String, mayo:Boolean = true, mustard:Boolean = true, lettuce:Boolean = true):Sandwich {...}
Now if you're happy to have all the condiments on your sandwich, you can keep your function call simple:
makeDagwood(3, "Turkey", "Muenster", "Wheat"); //has all condiments by default makeDagwood(3, "Roast Beef", "Swiss", "Rye", false, false, true); //no mayo or mustard
All parameters with default values must be placed at the end of the argument list, and will become optional to pass into the function.
You may also define functions that take variable numbers of
arguments. The arguments
array from
ActionScript 2 still exists. Every Function
object contains the arguments
property, which is an array of all
parameters passed to the function. However, ActionScript 3.0 requires
you to specify your parameters well, so the ...
keyword is provided. This allows you to
require some parameters but allow many others after that.
function addSandwichRecipe(sandwichName:String, ... layers):void {...}
Inside addSandwichRecipe()
, the
layers
array contains all the
additional parameters passed to the function. You can use any valid
identifier to store the additional parameters. A typical name for this
array is rest
, so you may hear the
...
keyword referred to as the rest
keyword. Because there is a normal argument before the rest keyword,
this method requires a recipe name:
addSandwichRecipe("PB&J", bread, peanutButter, jelly, bread); addSandwichRecipe("The English King", englishMuffin, peanutButter, banana, honey); addSandwichRecipe("The Ultra Slim"); //This is valid! I guess this is an Air sandwich.
A final note about parameters for ActionScript 2 developers: to
specify a function which requires zero parameters, use function foo()
, not function foo(Void)
as you would in AS2.
ActionScript 3.0 provides you with a unified, built-in, and standards-based event framework to drive your application with user input and decouple your collaborating classes.
You can subscribe to events with the addEventListener()
method. This method is
defined on all classes that implement IEventDispatcher
, which includes quite a lot
of classes. The new event model replaces all callback methods from
previous ActionScript versions. For instance, instead of defining the
onClick()
function on a button, we
use the event model:
myButton.addEventListener(MouseEvent.CLICK, onButtonClicked); function onButtonClicked(event:MouseEvent):void { myButton.label = "Quit clicking me!"; myButton.removeEventListener(MouseEvent.CLICK, onButtonClicked); }
The first line sets up an event listener for single clicks on the
button myButton
. When the button is
clicked, the method onButtonClicked()
is invoked. Notice that when you register an event listener, the event
handler method must accept an Event
as its parameter. In this case, we know it's going to be a MouseEvent
so we type the event
as a MouseEvent
, a subclass of Event
.
One simple, but key improvement in the built-in events is AS3's
usage of constants to define all event types. In this case, MouseEvent.CLICK
represents an event type to
listen for; but this event type is just a string. Somewhere in the
MouseEvent class it says:
public static const CLICK:String = "click";
All across the new AS3 API, and especially in the event packages, you will find that magic strings are replaced by constants. When you use constants, the compiler can catch typos before they become bugs.
Back to our button example: you might also have noticed that the
event handler, onButtonClicked()
,
refers to myButton
instead of
this
. The function, even though it
was invoked by a click on myButton
,
still executes in the scope of the code it was added to. ActionScript
3.0 creates a bound method for you whenever you call addEventListener()
, binding the listener
function to its owner and making writing event handlers more intuitive.
Just write them like any other method, and refer to them without using
Delegate.create()
. The object that
spawned the event will always be available to you through event.target
.
The example also shows removing an event listener after modifying the label of the button. It's important to clean up your event listeners when they are no longer valid. If you forget to remove an event listener, the object won't be removed by the garbage collector and can sit around, using up space. These uncollected objects can pile up and gradually affect the performance of your application.
To avoid this effect, one kind of memory
leak, ActionScript 3.0 allows you to attach an event listener
with a weak reference, which allows the object to be deleted even if the
event listener has not been explicitly removed. To enable a weak
reference, simply pass true to the last parameter of addEventListener()
, weakRef
:
myButton.addEventListener(MouseEvent.CLICK, onButtonClicked, false, 0, true);
The ActionScript 3.0 event model is tied tightly into the display list. Events travel through three phases after they are triggered, giving you granular control over when and where to capture and react to them. These three phases are collectively called the event flow. Think of it as a trip your event takes through the display list, with an opportunity to intercept, examine, and maybe even stop it at any point along the way.
I think the event flow is something like the game Bubble Bobble, shown in Figure 2. In Bubble Bobble, you control a little dragon with the ability to blow bubbles straight ahead. If you're in line with an enemy and your bubble travels far enough to hit it, the enemy gets trapped in the bubble. Then the bubble, complete with helpless enemy, floats up to the top of the screen, waiting for you to pop it. If this game were the event flow, the bubble would be the event, the enemy would be the subject of that event, and the playing field would represent your opportunities to examine or interrupt the event.
Let's say the user clicks on a button. First, you blow a bubble, which makes its way towards the enemy. This is called the capture phase. In the Flash Player, the event is traveling down the hierarchy of display objects, from the root of the display list (the stage) to the button that was clicked. If you set up an event listener for the capture phase on any object that is a parent of the button, you can respond to that click before the button does, even though it is eventually destined for the button.
Next, the bubble envelops the enemy. This is the target
phase. From here on, the target
of the event object is set to that
button, and from here on the enemy is stuck inside that bubble. If you
subscribed directly to the button without specifying the capture phase,
this is when you'll receive the event. This is the most straightforward
way of dealing with events, and the most familiar for ActionScript 2
developers.
Finally, the bubble rises to the top of the level in the
bubbling phase. With the correct target
still set inside the event, the event
is passed back up to the root of the display tree, so that the parents
of the button have a chance to examine or use the event. This bubbling
happens automatically as long as your event's bubbles
property is true. The bubbling phase
is useful when you want to deliver a custom event to the outside of a
complex subsystem. Say the alarm
event originates in a vibrating quartz crystal inside your watch.
Without doing any extra work, or breaking the encapsulation of your
watch by looking directly at the quartz, you can be notified that it's
time for a snack if you subscribe to your watch; the alarm event bubbles
out from the quartz crystal to the watch.
During any of these phases, many events may be canceled with the
stopPropogation()
and stopImmediatePropogation()
methods. This is
like your dragon character popping the bubble with its spiny back to
make sure other players aren't able to reach it. The event must be
declared cancelable
, though, so there
are some tough bubbles that you can't pop.
Yes, Virginia, ActionScript 3.0 provides features to perform reflection! Reflection is the ability for a program to be aware of and modify its behavior at runtime, specifically by discovering and creating objects and executable constructs.
Previous versions of ActionScript allowed you to perform some
rudimentary reflection by listing available methods of a class with a
for..in
loop, and by referencing
properties and methods of classes by name with bracket notation such as
obj["method"]()
. In ActionScript 3.0,
you may still access properties dynamically with bracket notation.
However, a simple loop will only iterate over dynamically added
properties.
This kind of reflection, introspection,
provides information about types and objects at runtime. AS3 provides
much better tools for introspection. The function flash.utils.describeType(classOrClassInstance)
provides ridiculously detailed information in XML on the class you pass
it, including its full type hierarchy, methods and parameters with
types, properties, and accessors. The mx.utils.ObjectUtil
class also provides a
getClassInfo()
function which does
much the same.
Beyond discovering information about a class whose reference you have, AS3 also lets you look up classes, namespaces, and functions, directly from strings. This can be extremely powerful, for example, to serialize application state and class dependencies into text, and to reconstruct the classes from text. Here, we store the name of one display object class and create another directly from its class name:
import flash.utils.*; var mySprite:Sprite = new Sprite(); var className:String = getQualifiedClassName(mySprite); trace(className); //flash.display.Sprite Alert; //force this class to compile in so that runtime code can use it. try { var ClassReference:Class = getDefinitionByName("mx.controls.Alert") as Class; addChild(new ClassReference()); } catch (error:ReferenceError) { trace("We couldn't look up that class"); }
Notice how we're able to store Classes as objects, and we can
actually instantiate them with new
.
3.141.47.25