4.4 Methods

 <  Day Day Up  >  

4.4 Methods

Methods are functions that determine the behavior of a class. They implement the tasks that instances of the class or the class itself can perform. For example, ActionScript's built-in Sound class has a method named loadSound( ) that can retrieve an external MP3 file for playback in the Flash Player.

To define methods for our own classes, we use the function statement within the body of a class definition, as follows :

 class   ClassName   {   function   methodName   (   param1   :   type   ,   param2   :   type   ,...   paramn   :   type   ):   returnType   {   statements   } } 

where methodName is the identifier for the method; param1 : type , param2 : type , ... paramn : type , is a list of the method's parameters; returnType is the datatype of the method's return value; and statements is zero or more statements executed when the method is invoked. Each parameter definition (e.g., param1 : type ) consists of a parameter name ( param1 ), a colon (:), and a type identifier ( type ), indicating the datatype of the parameter.

figs/as1note.gif In ActionScript 1.0, methods could be defined in several ways. Most commonly, methods were defined by assigning a function to a property of a class constructor's prototype , as follows:

   ClassName   .prototype.   methodName   = function (   param1   ,   param2   ,...   paramn   ) {   statements   }; 

Although the ActionScript 2.0 compiler does not generate an error when a prototype-based method is declared, it does generate an error when a prototype-based method is invoked on or referenced through an object whose class is not dynamic. Generally speaking, prototype-based methods should not be used when exporting to ActionScript 2.0.

Within the body of a method, parameters are referred to directly by name. For example, the following method, square( ) , defines a parameter, x , and refers to it directly in the method body:

 function square (x:Number):Number {   return x * x; } 

If a parameter's type is omitted in the method definition, no type checking is performed for that parameter (allowing the parameter to accept value of any datatype). Similarly, if returnType is omitted, no type checking is performed for the return value. (Whenever you omit the datatype, omit the colon as well.) However, if a method returns no value, its returnType should be set to Void (with a capital "V"), indicating that the method does not return a value.

Don't confuse the Void datatype with the rarely used void operator. The former starts with a capital "V," while the latter starts with a lowercase "v." Use Void (with a capital "V") when specifying a method return type for a method that doesn't return a value. If you use lowercase void instead, you'll get the following error, which can be very confusing if you don't know the subtle cause:

 A type identifier is expected after the ':'. 


If your method's returnType is Void , make sure it doesn't return any value. When a method specifies Void as its returnType but returns a value, the following error appears in the Output panel at compile time:

 A function with return type Void may not return a value. 

In most cases, the preceding error is a helpful debugging tool. It means that you probably designed the method to return no value but have strayed from the original design and implemented the method with a return value. If that happens, you should either:

  • Alter your design so that it reflects the method's return type (i.e., specify the return value's correct datatype in the function declaration)

  • Stick to your original design by altering your method so it does not return a value (in which case Void is the correct return datatype for the function declaration)

Conversely, when a method specifies a returnType other than Void but returns a value that does not match the specified type, the compiler generates the error:

 The expression returned must match the function's return type. 

And when a method specifies a returnType other than Void but contains no return statement whatsoever, the compiler generates the error:

 A return statement is required in this function. 

The general rule, then, is to be sure your method's implementation actually complies with the parameter and return types it specifies.

Right, enough theory. Let's see what a method definition looks like in real life by adding a getArea( ) method to our Box class:

 class Box {   private var width:Number = 0;   private var height:Number = 0;   // Here's the method definition   function getArea ( ):Number {     return width * height;   } } 

The getArea( ) method definition starts with the function keyword, followed by the method name, getArea . The getArea( ) method takes no parameters, so its parameter list is simply an empty set of parentheses, ( ) . The post-colon datatype, :Number , specifies that getArea( ) returns a value of the Number type. When called, getArea( ) returns the value of width multiplied by height , which will be a numeric value (as advertised).

By specifying our method's return type explicitly, we make debugging easier down the road. If we initially design our method to return a number, but then in the method body we accidentally return, say, a string, the compiler will warn us accordingly .

The final section of our method definition is the statement block, delineated by opening and closing curly braces:

 {   return width * height; } 

Notice that we refer to the properties width and height directly by name. Within the body of a method, properties are referred to directly, without any qualifying reference to an object (in the case of instance properties) or a class (in the case of class properties). In the next section, "Referring to the Current Object with the Keyword this," we'll learn one exception to this rule.

In a method definition, the method signature ” consisting of the method name and parameter list ”uniquely identifies the method among all other methods in the class. In some languages (most notably Java), a method is considered completely different from all other methods if any aspect of its signature differs from that of other methods. Hence, in Java, two methods might have the exact same name and the exact same number of parameters but be considered unique because, say, the first parameter of one method has a different datatype than the first parameter of the other! In Java, methods that have the same name but different signatures are known as overloaded methods. ActionScript doesn't support method overloading; every method in an ActionScript class must have a unique name (unique within that class). When two methods within a class have the same name, the compiler generates the following error:

 The same member name may not be repeated more than once. 

In our getArea( ) method definition, getArea( ) is the method's signature . The :Number datatype is not considered part of the signature.

You may wonder why the return datatype is not part of the signature. In a language that supports overloading, the compiler decides which method to execute based on the name of the method called and, if necessary, the datatypes of the arguments in the method call. Although in theory a compiler could also check the return datatype to differentiate between two methods with the same names and same parameter types, in practice nothing in the method invocation expression can sufficiently indicate which return type (and hence, which method) is required. Thus, the return datatype is not used to identify a method's signature, but simply to perform type checking on the return types.

Returning to our getArea( ) method, note that the whitespace adjacent to the curly braces in our getArea( ) definition is not dictated by ActionScript's grammatical rules; it's a matter of personal style. Some developers place the opening curly brace on the first line of the method definition, as shown earlier and throughout this book. Others place it on a line of its own, as in:

 function getArea ( ):Number    {     return width * height;   } 

Both styles are technically valid; only you can decide which god you pray to.

4.4.1 Referring to the Current Object with the Keyword this

From within a method body, you'll often want to refer to the object on which the method was invoked ”the so-called current object . Consider the expression b.getArea( ) . When that code runs, it invokes getArea( ) on the object b , but within the getArea( ) method body there is no such variable as b . So how do we refer to the current object ”the object on which getArea( ) was invoked ”from within getArea( ) ? To refer to the current object within a method, we use the this keyword. You might ask whether we couldn't alternatively pass in the object reference as a parameter. In theory, we could, but using this helps our class to remain encapsulated, eliminating the need for the outside world to worry about the object's internal requirements. The this reference to the current object allows an object to refer to itself without the need for an additional parameter, and is therefore much cleaner.

Let's take a look at several practical situations that require this . Note that neither of the following situations is exceedingly common. The this keyword is certainly important when you need it, but if you're a newer programmer, you may want to wait until that need arises before studying the next sections in detail.

4.4.1.1 Passing the current object to a method

The this keyword is most often used when passing the current object to another object's method, as in:

 function   someMethod   ( ):Void {   // Pass   this   as a parameter to a completely separate object   someOtherObject   .   someOtherMethod   (this); } 

Let's build an example that follows this pattern, in which we need to pass the current object ( this ) to a method of another class.

First, we'll add a resetDimensions( ) method to the Box class:

 function resetDimensions ( ):Void {   height = 1;   width  = 1;   trace("Dimensions reset!"); } 

Now suppose we want to call resetDimensions( ) whenever the user clicks the mouse. To handle mouse clicks, we give the Box class an onMouseDown( ) method that simply invokes resetDimensions( ) :

 function onMouseDown ( ):Void {   resetDimensions( ); } 

However, an instance of the Box class won't receive mouse events unless we register it to do so. Here we define another method, enableReset( ) , that can register a Box instance to receive Mouse event notifications:

 function enableReset ( ):Void {   Mouse.addListener(this); } 

The enableReset( ) method will be called elsewhere in our hypothetical program, but only on Box instances that should respond to mouse clicks. If we wanted all Box instances to respond to mouse clicks, then we would add an enableReset( ) call to the Box constructor function.

Now take a closer look at the body of the enableReset( ) method. The command:

 Mouse.addListener(this); 

passes the current Box object, this , to the Mouse class's addListener( ) method. The code says literally " Mouse class, please start sending mouse event notifications to the current object (i.e., to this , the Box instance that called enableReset( ) )." Henceforward, whenever the mouse is clicked, Box.onMouseDown( ) executes, resetting the Box instance's dimensions. Without the this keyword, we couldn't have told the Mouse class that the current object wants to receive mouse events.

To complete the example, we should also define disableReset( ) , a method to stop the Mouse class from sending events to a particular Box instance. We again use this to tell the Mouse class which Box wants to cancel event notifications:

 function disableReset ( ):Void {   Mouse.removeListener(this); } 

Here's how we'd use enableReset( ) :

 var b:Box = new Box( ); // When   enableReset( )   is called, the value of   this   in the   enableReset( )   // method definition stores a reference to our   Box   instance,   b   . b.enableReset( ); // From now on,   b.onMouseDown( )   fires whenever the mouse is clicked. 

When an object registers as a listener of another object, it should always unregister itself before it is deleted.


For example, in the preceding usage example, we registered b to receive mouse events. Suppose we were to delete our Box instance, b , as follows:

 delete b; 

Even though the reference to the Box instance stored in b is gone, another reference to that instance still exists in the Mouse class's list of registered listeners! We did not delete the Box instance, we only deleted the variable, b . The instance itself lives on due to the Mouse class's reference to it. This sloppiness can cause erratic behavior in a program and could become a serious waste of memory. A class should always provide a means of cleaning up stray object references before an object is deleted. That is, ActionScript doesn't garbage-collect an object (i.e., free up the memory used by an object) until no more references to it remain. Typically, cleanup is done in a custom die( ) or destroy( ) method that must be invoked before an object is deleted. (The name of the method is up to you, but it should indicate that it wipes the object's slate clean.) For example, here's a die( ) method for our Box class:

 function die ( ):Void {   disableReset( );  // Unregister the object so the   Mouse   class                     // deletes the reference to it. } 

By providing a die( ) method, a class guarantees a safe means of deleting its instances. A developer using the class simply calls die( ) before deleting each instance. For example:

 var b:Box = new Box( ); b.enableReset( ); b.die( ); delete b; 

Note that the die( ) method should not attempt to delete the Box instance itself. It is illegal for an object to delete itself. We'll return to this topic in Chapter 5.

4.4.1.2 Managing parameter/property name conflicts

When an instance property or a class property has the same name as a method parameter, the parameter takes precedence. That is, uses of the duplicate name in the method will refer to the parameter, not the property. However, we can still access the property by preceding the name with the this keyword (known as disambiguating the method parameter from the property.) For example, the following method, setHeight( ) , has a parameter, height , whose name is identical to the instance property height :

 class Box {   // Instance property   height   private var height:Number;   // Method with parameter   height   function setHeight (height:Number):Void {     // Method body not shown   } } 

Within the body of setHeight( ) , if we don't include a qualifier, the compiler assumes that the identifier height refers to the method parameter, not the instance property of the same name. The height parameter is said to shadow the height property. But we can still access the height property explicitly using the this keyword. The this keyword contains a reference to the current object; hence, just as with any other object, we can use the dot operator to access its properties or invoke its methods, as follows:

 function setHeight (height:Number):Void {   // Sets   height   property (   this.height   ) to the   // value of   height   parameter (   height   ).   this.height = height; } 

In the preceding setHeight( ) method, the value of the height parameter is assigned to the height property. The this keyword tells the compiler that this.height is the property, while height , on its own, is the parameter.

You will encounter many code examples in which the programmer purposely uses the same name for a parameter and an instance property. To keep things more clearly separated, however, you may wish to avoid using parameter names that have the same name as properties. For example, you could rewrite the preceding setHeight( ) method so it uses h instead of height as a parameter name:

 function setHeight (h:Number):Void {   // Sets   height   instance property (   height   ) to value of   h   parameter   height = h; } 

In this case, because the parameter is named h , not height , the unqualified reference ( height ) refers to the instance property. We'll see later under "Local Variables" that local variables can also shadow properties of the same name.

4.4.1.3 Redundant use of the keyword this

Even when the this keyword is not required, using it is perfectly legal. Therefore, in the example at the end of the preceding section, it is legal to explicitly qualify height by preceding it with this , as follows:

 this.height = h; 

Likewise, the following rewrite of our earlier Box.getArea( ) method is legal:

 function getArea ( ):Number {   return this.width * this.height; } 

However, using this when not required is redundant. For the sake of easier reading, many developers (and this book) avoid redundant uses of this . Even the single-line getArea( ) method is much less verbose without this :

 function getArea ( ):Number    {     return width * height;   } 

Methods that make redundant use of this require more work to produce and take longer to read. However, some programmers prefer to always use this when referring to instance properties and methods, simply to distinguish them from local variables. Other programmers prefer to use variable name prefixes instead, where local variable names start with l_ , and all property and method names start with m_ (meaning member).

figs/as1note.gif In ActionScript 1.0, within method and constructor function bodies, the this keyword was not redundant ”it was a required part of all instance property and method references.

Inside an ActionScript 2.0 class definition, use of the this keyword is legal only within instance methods and constructor functions. Within a class method (discussed in the next section), use of the this keyword generates the following compile-time error:

 'this' is not accessible from this scope. 

And anywhere else in a class definition, use of the this keyword generates this error:

 This statement is not permitted in a class definition. 

4.4.2 Method Attributes

Earlier, we saw how the public , private , and static attributes could control access to properties and make some properties pertain to an entire class rather than to individual instances of the class. Likewise, method definitions can be modified using the same three attributes, with an analogous effect. Method attributes must be listed before the function keyword in a method definition. For example, to add the private attribute to the getArea( ) method of our Box class, we'd use:

 class Box {  private  function getArea ( ):Number {     return width * height;   } } 

When a method has more than one attribute, the attributes can appear in any order. However, the public or private attribute is conventionally placed before the static attribute, as in:

  private static  function getArea ( ):Number {   return width * height; } 

Furthermore, the public and private attributes cannot both be used to modify the same method definition; they are mutually exclusive.

4.4.2.1 Controlling method access with the public and private attributes

The public and private method attributes are analogous to the public and private property attributes; they determine from where a method can be accessed. A public method is accessible to all code, anywhere. A private method can be accessed from within the class that defines the method (and its subclasses). If a method definition does not include the private attribute, the method is considered public. That is, all methods are public unless otherwise specified. If, however, we wish to show that a method is public by intention , not just by default, we can use the optional public attribute, as shown next. In general, it's good form to specify the public or private attribute explicitly for every method.

 class Box {   // Define properties...   private var width:Number = 10;   private var height:Number = 10;   // Define   getArea( )   with an explicit use of the   public   attribute  public  function getArea ( ):Number {     return width * height;   } } 

When a private method is accessed outside of the class that defined it (or any of its subclasses), the following error occurs at compile time:

 The member is private and cannot be accessed. 

The public and private attributes are used to put the OOP "black box" principle into strict practice. In OOP, each object can be thought of as a black box that is controlled by an external assortment of metaphoric knobs. The object's internal operations are unknown (and unimportant) to the person using those knobs ”all that matters is that the object performs the desired action. A class's public methods are the knobs by which a programmer can specify inputs (i.e., tell the class to perform some operation). A class's private methods can perform other internal operations. Each class should publicly expose only methods that the outside world needs to instruct it to do something. Methods needed to carry out those instructions should be kept internal (i.e., defined as private ). For example, a driver doesn't need to know how a car's engine works; to drive the car, he simply uses the gas pedal to accelerate and the steering wheel to turn . Making the car accelerate when he steps on the gas pedal is the manufacturer's concern, not his. The somewhat arbitrary nature of the external "knobs" is apparent if you compare a car to, say, a motorcycle. On a motorcycle, the rider typically accelerates by turning the handle grip rather than depressing a gas pedal. In both vehicles, however, the driver's action (the input) supplies gasoline to the engine, which is ultimately used to power the wheels (the output).

As you manufacture your own classes, you should focus as much energy designing the way the class is used as you do implementing how it works internally. Remember to put yourself in the "driver's seat" regularly. Ideally, the signatures for your class's public methods should change very little or not at all each time you make an internal change to the class. If you put a new engine in the car, the driver should still be able to use the gas pedal. Stay mindful that each change to a public method's signature will force a change everywhere that the method is used. As much as possible, keep the volatility of your classes behind the scenes, in private methods. Likewise, strive to create stable, generic public methods that aren't likely to be visibly affected if, say, the gravity of the physics environment changes or a new field is added to the database.

In OOP terms, an object's public methods and properties are sometimes called the object's interface to the outside world. In ActionScript, the term interface has a more specific technical meaning, covered in Chapter 8. To avoid confusion, we'll use the term "interface" in the ActionScript-specific sense only.


The ImageViewer class in Chapter 5 uses private methods to render an image on screen, crop it to a particular height and width, and surround it with a border. In the following excerpt from the ImageViewer class, the buildViewer( ) , createMainContainer( ) , createImageClip( ) , createImageClipMask( ) , and createBorder( ) methods are all private. In theory, this means we could entirely change how the image is rendered without affecting any code that uses ImageViewer objects:

 private function buildViewer (x:Number,                                y:Number,                                w:Number,                                h:Number):Void {   // Create the clips to hold the image, mask, and border.    // Each of the following methods is declared private (not shown).   createMainContainer(x, y);   createImageClip( );   createImageClipMask(w, h);   createBorder(w, h); } 

Consider what would happen if our createBorder( ) method were public and a developer used it to resize the border around the image. That seems sensible enough, but the developer probably doesn't know that in order to resize the border, she must also resize the image mask! The latter is an internal detail that's specific to our implementation and shouldn't be the developer's concern. What the developer really needs is a public method, setSize( ) , that handles resizing the image viewer but hides the implementation details required to do so. The setSize( ) method describes the object's behavior in generic terms rather than in relation to our specific implementation. Hence, setSize( ) 's external use is unlikely to change even if we fundamentally change the rendering approach in the ImageViewer class. It's wise to define the createBorder( ) method as private in the first place, averting problems caused by its unintended use. Then, when dynamic resizing becomes a requirement of our class, we can add public methods to expose that functionality to the world at large (which is filled with dangerous, nosy, meddling developers).

4.4.2.2 Defining class methods with the static attribute

Earlier, we saw that the static attribute determines whether a property is associated with instances of a class or with the class itself. Likewise, when used with methods, the static attribute determines whether the method is associated with instances of the class or with the class itself. Normally, method definitions create so-called instance methods ” methods that are accessed only through instances. By contrast, methods defined with the static attribute create so-called class methods , which are accessed through a class rather than through a particular instance (just like the class properties we learned about earlier).

Class methods (sometimes referred to as static methods ) provide functionality that relates to an entire class, not just an instance of that class. For example, ActionScript's built-in TextField class defines a class method named getFontList( ) , which returns a list of fonts on the user's system. The result of the getFontList( ) method is the same for all text fields, not specific to each text field. Therefore, it should logically be declared static . Because getFontList( ) is static , we access it through the TextField class directly, not through a TextField instance. For example, here we store the return value of getFontList( ) in a variable, fonts :

 var fonts:Array = TextField.getFontList( ); 

There are two reasons you should care if a method is a class method:

  • You need to know whether it is a class method in order to access it properly (if it's a class method, access it through the class; otherwise, access it through an instance of the class).

  • Knowing whether it is a class method tells you whether it affects a particular instance or whether it applies to the entire class.

Knowing that TextField.getFontList( ) is a class method (as identified by Appendix A) a developer should infer that it returns a font list that is independent of a single text field. If getFontList( ) were not a class method (that is, if it were accessed through an instance of the TextField class), a developer might instead assume that it returned a list of fonts used in a particular text field rather than all the fonts on the user's system.

Class methods can also be used to provide access to private class properties or to compare some aspect of two instances of a class. For example, the following code adds a class method, findLarger( ) , to our Box class. The findLarger( ) method returns the larger of two Box instances, or if the instances are the same size , it returns the first instance:

 class Box {   public static function findLarger (box1:Box, box2:Box):Box {     if (box2.getArea( ) > box1.getArea( )) {       return box2;     } else {       return box1;     }   }   // Remainder of class not shown... } // Usage example: var biggestBox:Box = Box.findLarger(boxInstance1, boxInstance2); 

Let's add another class method, getNumBoxes( ) , to the Box class. The getNumBoxes( ) method returns the value of a private class property, numBoxes , which tracks how many box instances currently exist:

 class Box {   private static var numBoxes:Number = 0;   public static function getNumBoxes ( ):Number {     return numBoxes;   }   // Remainder of class not shown... } // Usage example: var boxCount:Number = Box.getNumBoxes( ); 

Some classes define class methods only (that is, they define no instance methods). In such a case, the class exists solely to contain a group of related functions, but objects of the class are never instantiated . For example, you might define a SystemSettings class that contains the following class methods: setSoundEnabled( ) , setIntroEnabled( ) , and setScaleOnResize( ) . Those class methods are accessed via SystemSettings directly, so a SystemSettings instance need never be created. The built-in Mouse class similarly defines the following class methods: show( ) , hide( ) , addListener( ) , and removeListener( ) . Those class methods are accessed through Mouse directly (as in, Mouse.hide( ) ), not through an instance of the Mouse class.

Class methods have two limitations that instance methods do not. First, a class method cannot use the this keyword, as discussed earlier. Second, class methods can access class properties only (unlike instance methods, which can access both instance properties and class properties). A moment's consideration reveals why this is so: an instance should be able to access its own properties (instance properties) plus the properties common to all objects of the class (the class properties, of which there is only one set). Likewise, a class method should be able to access class properties. But it makes no sense for a class method to access an instance property, because each instance has its own set of instance properties. There would be no simple way for a class method to know which instance's properties are of interest.

Therefore, any attempt to access an instance property from a class method results in the following compile-time error:

 Instance variables cannot be accessed in static functions. 

However, a class method can legally create an instance of a class and then access its instance properties, as shown here:

 class SomeClass {   private static var obj:SomeClass;   private var prop:Number = 10;   public static function doSomething ( ):Void {     // Create an instance of   SomeClass   and store it      // in the class property   obj   .     obj = new SomeClass( );     // Access an instance property, prop, of instance stored in   obj   .     trace(obj.prop);  // Displays: 10   } } 

In the preceding code, SomeClass defines a class property, obj , and an instance property, prop . When the class method doSomething( ) is invoked, it creates an instance of SomeClass , stores it in obj , and accesses the instance property, prop . We'll revisit this theoretical structure when we study the Singleton pattern in Chapter 17.

Let's be clear that class properties (declared using static var ) and instance properties (declared using var ) are both declared outside of class methods and instance methods. Any variables declared within a method using the var keyword are considered local variables. For example, you can't declare a class property by simply using var or static var within a class method. Use of the static keyword within a method causes the following error:

 Attribute used outside class. 

figs/as1note.gif ActionScript 1.0 did not have a formal means of defining class methods. By convention, class methods were defined on a class's constructor function, as follows:

 // Class constructor function Box ( ) { } // Class method Box.findLarger = function (box1, box2) {   if (box2.getArea( ) > box1.getArea( )) {     return box2;   } else {     return box1;   } }; 

ActionScript 1.0 class methods could be accessed via the class constructor only, as in Box.findLarger( ) (not findLarger( ) ). Furthermore, in an ActionScript 1.0 class method, the this keyword was legal; it referred to the class constructor function.

 // Class constructor function Box ( ) { } // Class method Box.showThis = function ( ) {   trace(this == Box);  // Displays: true }; 

4.4.3 Accessor Methods

Earlier we learned that it's good OOP practice to declare a class's instance properties as private , meaning that they cannot be read or modified by code outside of the class. However, most objects must expose some means of examining and changing their state (i.e., provide a publicly accessible means of retrieving and assigning the object's properties). To allow external code to read and modify private properties, we create so-called accessor methods, which come in two varieties:

  • Those that retrieve a property's value

  • Those that set a property's value

Traditionally, an accessor method that retrieves a property's value is known as a getter method , and an accessor method that sets a property's value is known as a setter method . In ActionScript 2.0, however, those terms have a more specific technical meaning. They refer to special methods that are invoked automatically when a property is accessed, as described under "Getter and Setter Methods," later in this chapter. To avoid confusion, we'll use the terms "getter method" and "setter method" when referring only to ActionScript's special automatic methods.

In general, an accessor method that retrieves a property's value looks like this:

 public function get   PropertyName   ( ):   returnType   {   return   propertyName   ; } 

By convention, the retrieval method is named get PropertyName , where get is used literally and PropertyName is the name of the property being retrieved, except that the first letter is capitalized.

An accessor method that assigns a property's value looks like this:

 public function set   PropertyName   (   param   :   type   ):Void {   propertyName   =   param   ; } 

By convention, the assignment method is named set PropertyName , where set is used literally and PropertyName is the name of the property whose value is being assigned (again, with the first letter capitalized). When the method is called, the parameter param receives the value being assigned to the property.

Recall that our Box class defines two properties, width and height , both of which are declared private . Let's add accessor methods for those properties so that code outside of the Box class can retrieve and assign their values. In accordance with accessor-method naming conventions, we'll call our methods getWidth( ) , setWidth( ) , getHeight( ) , and setHeight( ) . Here's the code:

 class Box {   private var width:Number;   private var height:Number;   // Accessor to retrieve   width   public function getWidth ( ):Number {     return width;   }   // Accessor to assign   width   public function setWidth (w:Number):Void {     width = w;   }   // Accessor to retrieve   height   public function getHeight ( ):Number {     return height;   }   // Accessor to assign   height   public function setHeight (h:Number):Void {     height = h;   } } 

The getWidth( ) method simply returns the value of the width property. The setWidth( ) method defines a parameter, w , which can accept values of the Number datatype only. When invoked, setWidth( ) stores value of w in the private property width :

 width = w; 

Accessor methods that assign a property value traditionally have one of the following return values (we chose the first option):

  • No return value (i.e., return type Void ), if the assignment operation has no result, as is the case for setHeight( )

  • A Boolean, indicating whether the operation was successful (according to the method's own logic)

  • The old property value

  • The new property value (perhaps adjusted to make it fall within legal range)

The getHeight( ) and setHeight( ) methods are structured exactly like the getWidth( ) and setWidth( ) methods but apply to the height property rather than to width .

Here's a sample use of our new accessor methods:

 var b:Box = new Box( ); b.setWidth(300); b.setHeight(200); trace(b.getWidth( ));   // Displays: 300 trace(b.getHeight( ));  // Displays: 200 

Here, we can start to see the real benefits of a typed language. By specifying the w parameter's datatype as Number , we guarantee that the setWidth( ) method assigns only numeric values to the width property, as required by its property definition ( private var width:Number ). If some code erroneously attempts to pass setWidth( ) nonnumeric data, the compiler will warn us of the specific location of the problem! For example, suppose we place the following code on frame 1 of a movie:

 var b:Box = new Box( ); b.setWidth("really short"); 

When we attempt to export the movie, the compiler displays the following error in the Output panel:

 **Error** Scene=Scene 1, layer=Layer 1, frame=1:Line 2: Type mismatch.      b.setWidth("really short"); 

Specifying w 's datatype guarantees that our class cannot be used in ways that will cause runtime problems that would be difficult to track down. We can further insulate our program from the effects of erroneous data by implementing custom limitations on the argument values supplied to the setWidth( ) method. For example, the following code restricts the legal values of w to numbers greater than 0 and less than infinity. It also ensures that w 's value is neither the special NaN numeric value nor null . While both NaN and null are legitimate values of the Number type, they would cause problems for our program. (Remember from Chapter 3 that null is a legal value of any datatype.)

 public function setWidth (w:Number):Boolean {   if (isNaN(w)  w == null        w <= 0  w > Number.MAX_VALUE) {     // Invalid data, so return   false   to indicate a lack of success     return false;   }   // Otherwise, it was successful, so return   true   to indicate success   width = w;   return true; } 

Our revised setWidth( ) method returns a Boolean value indicating whether width was set successfully. External code can rely on the Boolean result to handle cases in which invalid data is passed to the method. For example, the following code attempts to set the width property based on a user input text field. If the input is not valid, the code can recover, presumably by displaying an error message:

 var b:Box = new Box( ); // Check the Boolean return value of the call to   setWidth( )   if (b.setWidth(parseInt(input_txt.text))) {   // No problems...proceed as planned } else {   // ERROR! Invalid data...display a warning (not shown) } 

By using an accessor method to mediate property-value assignments, we can develop applications that respond gracefully to runtime problems by anticipating and handling illegal or inappropriate values. But does that mean each and every property access in a program should happen through an accessor method? For example, consider our earlier getArea( ) method:

 public function getArea ( ):Number {   return width * height; } 

Now that we have accessors for the width and height properties, should getArea( ) be changed as follows?

 public function getArea ( ):Number {   return getWidth( ) * getHeight( ); } 

The answer depends on the circumstances at hand. Generally speaking, it's quite reasonable to access private properties directly within the class that defines them, so the preceding rewrite of getArea( ) is not necessarily required nor even recommended. In cases in which speed is a factor, direct property access may be prudent (accessing a property directly is always faster than accessing it through a method). However, when a property's name or datatype is likely to change in the future or when an accessor method provides special services during property access (such as error checking), it pays to use the accessor everywhere, even within the class that defines the property. For example, remember that we modified our earlier setWidth( ) method to include error checking for invalid data. Whenever we assign the value of width within the Box class, we still want the benefit of setWidth( ) 's error checking. That is, although accessing properties directly from within a class conforms to OOP guidelines, it's sensible to use the accessor methods instead.

If you simply prefer the style of direct property access but still want the benefits of accessor methods, you should consider using ActionScript 2.0's automatic getter and setter methods, discussed later.


4.4.4 Local Variables

We've seen that instance properties are associated with each instance of a class and that class properties are associated with the class itself. Instance and class properties persist as long as the object they're associated with persists. But often you'll need a variable only temporarily. A local variable is a temporary data container used to store a value for the duration of a function or method execution. Local variables can be defined within a function or method only and are normally marked for automatic deletion when the function or method finishes executing (we'll learn the one exception to this rule in the next section, "Nesting Functions in Methods"). To define a local variable in a method, use the var statement in the method body. Note that var is used for declaring class and instance properties too, but property declarations must be outside of any method definitions; when used within a method, the var statement creates a local variable, not a property. For example:

 function   methodName   ( ):   returnType   {   var   localVariableName   :   type   ; } 

where localVariableName is the identifier (name) for the local variable and type is the datatype declaration (optional, but recommended).

A local variable definition can also include an initial value, as follows:

 var   localVariableName   :   type   =   value   ; 

For example, the following modified version of our earlier Box.getArea( ) method stores the value of height * width in a local variable, area , which it then returns:

 public function getArea ( ):Number {   var area:Number = width * height;   return area; } 

Local variables are used for code clarity and to improve code performance by eliminating repeated method calls or property lookups. For example, in the following method excerpt from a chat application, we display a user's name in a List component ( userList ) when the user enters a chat room. The user's name is retrieved via the expression remoteuser.getAttribute( ) . Instead of calling that method repeatedly, we call it once and store the return value in a local variable, username :

 public function addUser ( ):Void {   var username:String = remoteuser.getAttribute("username");   // Use "Guest" as the username if the   remoteuser   hasn't set a name   if (username == undefined) {     username = "Guest";   }   // Add the user to the listbox  userList.addItem(username); } 

Consider the alternative approach, which doesn't bother with the local variable username :

 public function addUser ( ):Void {   // Use "Guest" as the username if the   remoteuser   hasn't set a name   if (remoteuser.getAttribute("username") == undefined) {     // Add the new user to the listbox     userList.addItem("Guest");   } else {     // Add the new user to the listbox     userList.addItem(remoteuser.getAttribute("username"));   } } 

The second version (no local variable used) is less readable and requires separate calls to both userList.addItem( ) and remoteuser.getAttribute( ) , which is error prone. In the first version (local variable used), the step of adding the username to the List is neatly kept to a single statement and the code is broken into two logical steps: determining the user's name and adding the user's name to the List.

Parameters are also treated as local variables, even though they are not declared with the var keyword.

As of Flash Player 6.0.65.0, local variables and function parameters are stored in internal registers for quick runtime access (registers are special hardware locations set aside in the computer's CPU rather than its RAM). Hence, when performance is a concern, you should use local variables in favor of repeated object references. For example, instead of:

 for (var i:Number = 0; i < clipsArray.length; i++) {   clipsArray[i]._x = i * 10;   clipsArray[i]._y = i * 10;   clipsArray[i]._rotation = i * 10; } 

It's faster to use:

 for (var i:Number = 0; i < clipsArray.length; i++) {   var clip:MovieClip = clipsArray[i];   clip._x = i * 10;   clip._y = i * 10;   clip._rotation = i * 10; } 


Note that if you define a local variable of the same name as a property, the local variable hides the property for the entire duration of the method, even before the local variable is defined! To access the property when a local variable of the same name exists, you must qualify it with the this keyword. For example, the following code does not work as expected because it is missing the this keyword. The code assigns the undefined variable power to itself:

 class Enemy {   private var power:String = "high";   public function shoot ( ):Void {     trace(power);  // Displays: undefined                     //(The value of the local variable hides the property                    // even before the local variable definition!)     var power:String = power;  // Assigns undefined variable to itself.   } } 

By contrast, the following code works because it uses this.power to access the power instance property (to distinguish it from the local variable of the same name):

 class Enemy {   private var power:String = "high";   public function shoot ( ):Void {     trace(this.power);              // Displays: high     var power:String = this.power;  // Assigns property to variable   } } 

In order to avoid confusion, you should avoid giving properties and local variables the same name. See "Managing parameter/property name conflicts," earlier in the chapter, for related details.

4.4.5 Nesting Functions in Methods

ActionScript supports nested functions , which means that functions can be declared within methods or even within other functions. The following code creates a method, Box.getArea( ) , that contains a nested function, multiply( ) . Inside getArea( ) , the nested function can be invoked as usual. However, like a local variable, a nested function is accessible only to its parent function (the function in which it is declared). Code outside the parent function cannot execute the nested function:

 class Box {   private var width:Number;   private var height:Number;   public function getArea ( ):Number {     return multiply(width, height);     // Here's the nested function definition.     // Only the   getArea( )   method can invoke it. Other methods cannot.     function multiply (a:Number, b:Number):Number {       return a * b;     }   } } 

Nested functions are used commonly when assigning callback functions to setInterval( ) or to objects that support event handler properties, such as MovieClip , Sound , and XML (callback functions are merely functions triggered when a particular event has occurred). For example, the following code shows a simple TimeTracer class with a startTimeDisplay( ) method. The startTimeDisplay( ) method uses setInterval( ) to call a nested function, displayTime( ) , once per second (every 1000 milliseconds ). The displayTime( ) function displays the current time in the Output panel:

 class TimeTracer {   public function startTimeDisplay ( ):Void {     setInterval(displayTime, 1000);          // Declare   displayTime( )   as a nested function     function displayTime ( ):Void {       trace(new Date( ).toString( ));     }   } } // Usage: var tt:TimeTracer = new TimeTracer( ); tt.startTimeDisplay( ); 

All local variables and parameters defined in a method are available to nested functions, even after the method finishes executing ! Methods can, hence, use local variables to store data that will be used later by a nested function. For example, to make displayTime( ) report the time of its first execution, we can store that information in a local variable, begunAt . The displayTime( ) function can safely access begunAt 's value long after the startTimeDisplay( ) method has completed:

 class TimeTracer {   public function startTimeDisplay ( ):Void {     // Define the local variable,   begunAt   .     var begunAt:String = new Date( ).toString( );     setInterval(displayTime, 1000);          function displayTime ( ):Void {       // Here, the nested function refers to the local variable   begunAt   ,        // which is defined in the enclosing method,   startTimeDisplay( )   .       trace("Time now: " + new Date( ).toString( ) + ". "             + "Timer started at: " + begunAt);     }   } } 

Note that the begunAt variable persists because it is defined in the method outside of displayTime( ) . If begunAt were declared within displayTime( ) , its value would be reset each time displayTime( ) is invoked.

Now let's add a means of stopping the time display. To do so, we must:

  1. Store the interval ID returned by setInterval( ) in a property, timerInterval

  2. Add a stopTimeDisplay( ) method that uses clearInterval( ) to cancel the periodic calls to displayTime( )

  3. Invoke stopTimeDisplay( ) each time startTimeDisplay( ) is called (thus preventing multiple intervals from running at the same time)

Here's the code:

 class TimeTracer {   private var timerInterval:Number;   public function startTimeDisplay ( ):Void {     stopTimeDisplay( );     var begunAt:String = new Date( ).toString( );     timerInterval = setInterval(displayTime, 1000);          function displayTime ( ):Void {       trace("Time now: " + new Date( ).toString( ) + ". "             + "Timer started at: " + begunAt);     }   }   public function stopTimeDisplay ( ):Void {     clearInterval(timerInterval);   } } 

When stopTimeDisplay( ) is called, the interval is halted, and the nested displayTime( ) function is automatically deleted. Moreover, the local variable begunAt , which was preserved for use by displayTime( ) , is no longer needed and is therefore also automatically garbage-collected (i.e., deleted).

4.4.5.1 Accessing the current object from a function nested in a method

A function nested in a method does not have direct access to the current object (the object on which the method was called). However, we can handcraft a hook back to the current object by storing a reference to it in a local variable.

Let's return to our Box class to show how a function nested in a method can access the current object via a local variable. We'll give the Box class a debugDimensions( ) method that periodically displays the dimensions of a Box instance in the Output panel. The code is similar to TimeTracer.startTimeDisplay( ) , but this time the nested function, displayDimensions( ) , needs to display the current Box object's width and height . In order to give the displayDimensions( ) function access to the Box object, we store a reference to this in a local variable, boxObj . That variable is accessible every time displayDimensions( ) runs:

 public function debugDimensions ( ):Void {   var boxObj:Box = this;   setInterval(displayDimensions, 1000);        function displayDimensions ( ):Void {     // Access the current object through the local variable   boxObj   .     trace("Width: " + boxObj.width + ", Height: " + boxObj.height);   } } 

Note that if we were feeling tricky, we could bypass the local variable approach altogether and simply pass the current Box object ( this ) to the displayDimensions( ) function, as shown next. The effect is the same, though the code is perhaps harder to read for less experienced ActionScript programmers. It relies on the fact that setInterval( ) passes the third argument (and subsequent arguments) to the function specified in the first argument. In this case, this , which contains a reference to the current Box object, is passed as the first parameter to displayDimensions( ) :

 public function debugDimensions ( ):Void {   // The third argument is passed onto   displayDimensions( )   setInterval(displayDimensions, 1000, this);   // This function receives the current object (   this   ) as its first argument       function displayDimensions (boxObj):Void {     trace("Width: " + boxObj.width + ", Height: " + boxObj.height);   } } 

The concept of storing the current object in a local variable for use by nested functions is pretty darn important ”it enables two-way communication between a method and a callback function defined within that method. Let's look at one more example. This time, we'll create a new Box method, loadDimensions( ) , that loads the dimensions for a Box instance from an external XML file. Inside loadDimensions( ) we create an XML object, dimensions_xml . We assign dimensions_xml an XML.onLoad( ) event handler using a nested function. Then, when the XML data loads, dimensions_xml.onLoad( ) automatically executes, and we use the loaded XML data to set the current Box object's height and width properties. We access the current Box object using the local variable boxObj :

 public function loadDimensions (URL:String):Void {   // Store a reference to the current box object in   boxObj   .   var boxObj:Box = this;   // Create and prepare the   XML   object.   var dimensions_xml:XML = new XML( );   dimensions_xml.ignoreWhite = true;   // Assign a nested function as the   XML   object's   onLoad( )   handler.   // It will be called automatically when the XML file loads.   dimensions_xml.onLoad = function (success:Boolean):Void {     if (success) {       // Assign the newly loaded dimensions to the   width   and   height   // properties of the   Box   object that called   loadDimensions( )   .       boxObj.width =             parseInt(this.firstChild.firstChild.firstChild.nodeValue);       boxObj.height =             parseInt(this.firstChild.childNodes[1].firstChild.nodeValue);     } else {       // Handle a load error.       trace("Could not load dimensions from file: " + URL);     }   }      // Load the requested XML file.   dimensions_xml.load(URL); } // Here's a sample XML file, showing the structure expected by   loadDimensions( )   .   <?xml version="1.0"?>   <DIMENSIONS>     <WIDTH>8</WIDTH>     <HEIGHT>9</HEIGHT>   </DIMENSIONS> 

The onLoad( ) handler executes when the XML file is done loading ”long after loadDimensions( ) has finished its own execution. However, even though loadDimensions( ) has finished executing, the local variable boxObj and even the method parameter URL (which is also a local variable) continue to be available to the nested onLoad( ) function. If we hadn't stored the current object in a local variable, our callback function wouldn't have had access to the Box object, and the width and height properties could not have been set.

Note that the dimensions_xml object is kept alive until its load( ) operation completes and onLoad( ) fires. Once onLoad( ) has executed, if no other references to the dimensions_xml object exists (as in the case is in our example), the interpreter automatically marks the object for deletion, preventing memory waste.

4.4.6 Getter and Setter Methods

Earlier we learned about accessor methods, which are public methods that assign and retrieve the value of a private property. Some developers consider accessor methods cumbersome. They argue that:

 b.setHeight(4); 

is more awkward than:

 b.height = 4; 

However, in our earlier study, we saw that direct property assignments such as b.height = 4 aren't ideal OOP practice and can lead to invalid property assignments. To bridge the gap between the convenience of property assignment and the safety of accessor methods, ActionScript 2.0 supports "getter" and "setter" methods. Getter and setter methods are accessor-like methods, defined within a class body, that are invoked automatically when a developer tries to get or set a property directly.

To define a getter method, we use the following general syntax:

 function get   propertyName   ( ):   returnType   {   statements   } 

where the get keyword identifies the method as a getter method, propertyName is the name of a pseudo-property serviced by the getter method, returnType is the datatype returned by the method, and statements is zero or more statements executed when the method is invoked (one of which is expected to return the value associated with propertyName ).

To define a setter method, we use the following general syntax:

 function set   propertyName   (   newValue   :   type   ):Void {   statements   } 

where the set keyword identifies the method as a setter method, propertyName is the name of a pseudo-property serviced by the setter method, newValue receives the value that the caller is requesting be assigned to the pseudo-property, and statements is zero or more statements executed when the method is invoked ( statements is expected to determine and internally store the value associated with propertyName ). As a developer, you can use the return statement alone in a setter method body, but you must not return any value. Setter methods have an automatic return value, discussed later.

Unlike other methods, getter and setter methods cannot be declared with the private attribute. An attempt to define a getter or setter as private yields the following error:

 A member attribute was used incorrectly. 

Getter and setter methods have a unique, property-access style of being invoked that does not require use of the function call operator, ( ) .

Getter and setter methods are invoked automatically when a programmer tries to access or set a property of the same name using the dot operator.


Therefore, a getter method, x( ) , on an object, obj , is invoked as:

 obj.x; 

rather than:

 obj.x( ); 

And a setter method, y( ) , on an object, obj , is invoked as:

 obj.y =   value   ; 

rather than:

 obj.y(   value   ); 

where value is the first (and only) argument passed to y .

Getter and setter methods, hence, appear to magically translate property accesses into method calls. For example, if we add a getter method named height( ) to our Box class, then all attempts to retrieve the value of the height property will actually invoke the getter method named height( ) . The getter method's return value will appear as though it were the value of the height property.

 // Invokes the getter   height( )   and displays  // its return value in the Output panel. trace(someBox.height); 

Similarly, if we add a setter method named height( ) to our Box class, attempts to assign the value of the height property invoke the setter method named height( ) . The value used in the height assignment statement is passed to the setter method, which is expected to store it internally in a private property.

 // Invokes the setter   height( )   , which should store 5 internally someBox.height = 5; 

With a getter and a setter method named height( ) defined, the height property becomes an external fa §ade only; it does not exist in the class but can be used as though it did. You can, therefore, think of properties that are backed by getter and setter methods (such as height ) as pseudo-properties .

It is illegal to create an actual property with the same name as a getter or setter method. Attempts to do so result in the following compile-time error:

 The same member name may not be repeated more than once. 


Example 4-2 revises our earlier Box class, adding getter and setter methods for the width and height properties. Notice that the values of the width and height pseudo-properties are stored internally in real properties named width_internal and height_internal because we can't use properties named width and height any longer.

Example 4-2. A class with getter and setter methods
 class Box {   // Note that   width   and   height   are no longer declared as properties   private var width_internal:Number;   private var height_internal:Number;   public function get width ( ):Number {     return width_internal;   }   public function set width (w:Number):Void {     width_internal = w;   }   public function get height ( ):Number {     return height_internal;   }   public function set height (h:Number):Void {     height_internal = h;   } } 

With our getter and setter methods in place, we can now use the width and height pseudo-properties as follows:

 var b:Box = new Box( ); b.width  = 20;    // Calls the   width   setter. trace(b.width);   // Calls the   width   getter. Displays: 20 b.height = 10;    // Calls the   height   setter. trace(b.height);  // Calls the   height   getter. Displays: 10 

figs/as1note.gif In ActionScript 1.0, getter and setter methods can be created with Object.addProperty( ) . In fact, ActionScript 2.0's support for getter and setter methods is effectively a wrapper around Object.addProperty( ) with some minor optimizations.

Example 4-3 shows the equivalent ActionScript 1.0 code for Example 4-2.

Example 4-3. ActionScript 1.0 code to simulate ActionScript 2.0 getter and setter methods
 _global.Box = function( ){}; _global.Box.prototype._ _get_ _width = function( ) {   return this.width_internal; }; _global.Box.prototype._ _set_ _width = function (w) {   this.width_internal = w; }; _global.Box.prototype._ _get_ _height = function( ) {   return this.height_internal; }; _global.Box.prototype._ _set_ _height = function (h) {   this.height_internal = h; }; _global.Box.prototype.addProperty("width",    Box.prototype._ _get_ _width,   Box.prototype._ _set_ _width); _global.Box.prototype.addProperty("height",    Box.prototype._ _get_ _height,   Box.prototype._ _set_ _height); 

When a setter method is called, it always invokes its corresponding getter and returns the getter's return value. This allows a program to use the new value immediately after setting it, perhaps in order to chain method calls. For example, the following code shows a fragment of a fictitious music player application. It uses a setter call to tell the music player which song to play first. It then immediately plays that song by calling start( ) on the return value of the firstSong assignment.

 (musicPlayer.firstSong = new Song("lovesong.mp3")).start( ) 

While convenient in some cases, the return-value feature of setters is a deviation from the ECMAScript 4 specification and is, therefore, considered a bug by Macromedia. This buggy behavior imposes limits on getters ” specifically , getters should never perform tasks beyond those required to retrieve their internal property value. For example, a getter should not implement a global counter that tracks how many times a property has been accessed. The automatic invocation of the getter by the setter would tarnish the counter's record keeping.

Getter and setter methods can be used both inside and outside of the class block that defines them. For example, we could quite legitimately add the following getArea( ) method definition to the Box class shown in Example 4-2; the direct references to the pseudo-properties width and height are legal and common practice.

 public function getArea ( ):Number {   return width * height; } 

In fact, getter and setter method usage can even be nested. For example, we could change the preceding getArea( ) method definition into a getter method definition as follows:

 public function get area ( ):Number {   return width * height; } 

In that case, we could use pseudo-properties ( width , height , and area ) to assign a Box instance a height and width, and even to retrieve its area:

 var b:Box = new Box( ); b.width  = 20; b.height = 10; trace(b.area);  // Displays: 200 

4.4.6.1 Simulating read-only properties

A getter/setter pseudo-property can be made read-only by declaring a getter without declaring a setter.

For example, the area property that we just defined does not have a setter because its value depends on the values of other properties. It doesn't make sense for a user of the class to set the area directly, so we disable assignment by not defining a setter. However, to explain this limitation to users who attempt to set the area property, we could define a do-nothing setter that prints a debug message to the Output panel, as follows:

 function set area ( ):Void {   trace("Warning: The area property cannot be set directly."); } 

Don't confuse read-only pseudo-properties with private properties (or, for that matter, public properties). A read-only pseudo-property can be read from outside the class but cannot be set. A private property cannot be read or set from outside the class, but a public property can be both read and set from anywhere.

4.4.7 Extra or Missing Arguments

In ActionScript 2.0 (and 1.0), a method can be invoked with more or fewer arguments than its signature specifies. The compiler doesn't complain if you pass the wrong number of arguments. It checks only whether the arguments provided match the specified types. If you pass more arguments than specified, the compiler type checks only as many arguments as are in the method signature. Extra arguments are not (indeed cannot be) type checked automatically by the compiler. Although there is no way to have the compiler warn you if the wrong number of arguments are passed, you can write custom code to do so. For example, the following method, setCubeDimensions( ) , aborts when passed anything other than three arguments (note the use of the arguments object, which we'll discuss shortly):

 public function setCubeDimensions (x:Number, y:Number, z:Number):Void {   // Wrong number of arguments passed, so abort   if arguments.length != 3 {     trace ("This function requires exactly 3 arguments.");     return;   }   // Remainder of method not shown... } 

When a method is invoked with fewer arguments than it expects, the missing arguments are automatically set to undefined . For example, recall our basic method definition for Box.setWidth( ) , which specifies one parameter, w :

 public function setWidth (w:Number):Void {   width = w; } 

The setWidth( ) method can legally be invoked without arguments as follows:

 someBox.setWidth( ); 

In this case, someBox.width is set to undefined by the setWidth( ) method. Methods should handle the possibility that they may be invoked with fewer arguments than expected. For example, if we wanted to prevent our setWidth( ) method from setting the width property when invoked with no arguments, we could adjust it as follows (additions shown in bold):

 public function setWidth (w:Number):Void {   // If   w   is undefined, then quit  if (w == undefined) {   return;   }  // If we got this far,   w   has a usable value, so proceed normally   width = w; } 

However, we needn't always abort a method simply because arguments are missing. By anticipating missing arguments, we can effectively provide two flavors of the same method: one that uses developer-supplied arguments and one that automatically fills in default values for missing arguments. For example, we could adjust setWidth( ) as follows:

 public function setWidth (w:Number):Void {   // If   w   is undefined...   if (w == undefined) {     // ...default to 1.     width = 1;   } else {     // ...otherwise, use the supplied value of   w   .     width = w;   } } 

We can then invoke setWidth( ) as either setWidth( ) or setWidth(5) . Java developers will recognize this as ActionScript's (partial) answer to method overloading.

Note that the equality operator ( == ) considers null equal to undefined . Hence, the invocations, setWidth( ) and setWidth(null) are equivalent. In the former, the developer merely implies that he wishes to omit the value of w ; in the latter, the developer explicitly specifies that he's not providing a value for w . Either way, our redesigned setWidth( ) method has the same result.

When a method accepts multiple parameters, null can be used as a placeholder for missing arguments that precede other arguments. Here, we call a method that expects three arguments (method definition not shown) but we pass the empty placeholder null as the first argument:

 methodThatExpectsThreeArguments (null, 4, 5); 

Whenever a method is invoked, all the arguments passed in the invocation are available via the arguments object. The arguments object stores all arguments passed to the method, even when passed more arguments than it expects (i.e., beyond those declared in the method definition). To access any argument, we examine the elements of the arguments array, as follows:

 arguments[   n   ] 

where n is the index of the argument we're accessing. The first argument (the leftmost argument in the method invocation) is stored at index 0 and is referred to as arguments[0] . Subsequent arguments are stored in order, proceeding to the right ”so, the second argument is arguments[1] , the third is arguments[2] , and so on. Here's another rewrite of our setWidth( ) method; it displays the first and second arguments passed to the method:

 public function setWidth (w:Number):Void {   trace("Argument 1 is: " + arguments[0]);   trace("Argument 2 is: " + arguments[1]); } // Usage: someBox.setWidth(15, 25);  // Displays:                            // Argument 1 is: 15                            // Argument 2 is: 25 

From within a method, we can tell how many arguments were passed to the currently executing function by checking the number of elements in arguments , as follows:

 var numArgs:Number = arguments.length; 

We can easily cycle through all the arguments passed to a method using a for loop. Example 4-4 shows a method, sendMessage( ) , that sends an XML message to a chat server, in the following format:

 <MESSAGE>   <ARG>value 1</ARG>   <ARG>value 2</ARG>   ...   <ARG>value n</ARG> </MESSAGE> 

The sendMessage( ) method defines no parameters. Instead, it retrieves all its argument values via the arguments object. Any number of arguments can be passed to sendMessage( ) ; each one becomes the content of an <ARG> tag in the XML message sent.

Example 4-4. A method that accepts an unknown number of arguments
 public function sendMessage ( ):Void {   // Build the message to send   var message:String = "<MESSAGE>";   for (var i:Number = 0; i < arguments.length; i++) {     message += "<ARG>" + arguments[i] + "</ARG>";   }   message += "</MESSAGE>";   // Display what we're sending in the Output panel   trace("message sent: \n" + message);   // Send the message to the server   socket.send(message); } 

As a point of interest, the arguments object also stores:

  • A reference to the method currently executing ( arguments.callee )

  • A reference to the method that called the method currently executing, if any ( arguments.caller )

Note that arguments.callee is not the same as this . The latter refers to the object on which the method is invoked, whereas the former is a reference to the current method itself.

For details, see ActionScript for Flash MX: The Definitive Guide (O'Reilly).

 <  Day Day Up  >  


Essential ActionScript 2.0
Essential ActionScript 2.0
ISBN: 0596006527
EAN: 2147483647
Year: 2004
Pages: 177
Authors: Colin Moock

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net