12.7 The Almighty Prototype Chain

ActionScript for Flash MX: The Definitive Guide, 2nd Edition
By Colin Moock
Chapter 12.  Objects and Classes

In Chapter 2, we saw that the interpreter uses a scope chain to resolve (look up the value of) unqualified variable references. To resolve property values, the interpreter uses a prototype chain. We've already encountered the prototype property in our examination of methods and superclasses. Now let's take a look "under the hood" of ActionScript's inheritance system to see how the prototype property really works. For the sake of the current discussion, the term property refers to object properties, methods, and event handlers (the latter two of which are, technically, functions stored in properties).

As we've seen, every class constructor function is automatically given a property called prototype. For example, when we create a Book class, the Book constructor automatically defines Book.prototype:

function Book ( ) { } trace(Book.prototype);  // Displays: [object Object]

Initially, a constructor function's prototype property contains a generic object (i.e., an instance of the generic Object class). Earlier, we added methods to the prototype object that were accessible to all instances created by the constructor. How does this work?

Suppose we define a property directly on an object, either outside of a constructor, like this:

var importantDates = new Object( ); // Define gwenBirthday property. var importantDates.gwenBirthday = "January 17";

or inside a constructor, like this:

function Book (author) {   // Define author property.   this.author = author; } var asGuide = new Book("Colin Moock");

When we ask the interpreter to retrieve any property, it first checks if the object, itself, defines the property. For example, in the following code, the interpreter checks asGuide for the author property, finds it, and displays its value in the Output window:

trace(asGuide.author);  // Displays: Colin Moock

But if the interpreter cannot find the specified property on the object instance, it searches the object's class constructor's prototype property. For example, suppose we define a property, description, on Book.prototype:

Book.prototype.description = "Bound paper with writing on it.";

Then we check the value of asGuide.description:

trace(asGuide.description);  // Displays: Bound paper with writing on it.

The interpreter can't find description directly on asGuide, so it looks for it on Book.prototype. Book.prototype defines description, so the interpreter uses it as the value of asGuide.description. But if we add a description property directly to an instance of Book, either inside or outside the Book constructor, the instance property overrides the Book.prototype property. For example:

function Book (author, desc) {   // Define author property   this.author = author;   // Define description property   this.description = desc; } Book.prototype.description = "Bound paper with writing on it.";     var asGuide = new Book("Colin Moock", "A book about ActionScript"); trace(asGuide.description);  // Displays: "A book about ActionScript"                              // NOT: "Bound paper with writing on it."

Let's see how this applies to methods by adding a getAuthor( ) method to Book:

Book.prototype.getAuthor = function ( ) {   return this.author; }

When we call asGuide.getAuthor( ), the interpreter checks asGuide to see if it defines a property named getAuthor. It does not, so the interpreter checks Book.prototype for getAuthor. There the interpreter finds our custom method and executes it.

What would have happened if Book.prototype had not defined getAuthor? Astute readers will recall that, by default, Book.prototype stores a generic object instance. When the interpreter cannot find a property on that generic instance, it checks the instance's own class constructor's prototype property which is, you guessed it, Object.prototype. That's how the prototype chain works: when a property is not found on an object instance, the object's class's prototype is searched. If the property is not there, the prototype object's own class's prototype is searched, and so on up the chain, until Object.prototype is reached. If Object.prototype does not define the property, the property is undefined. Here's the prototype chain for our asGuide instance (note that prototype chains, like scope chains, are read from the bottom up):

Object.prototype      // If not found here, return undefined Book.prototype        // If not found here, look on Object.prototype asGuide               // If not found here, look on Book.prototype                       // INTERPRETER STARTS LOOKING FOR PROPERTIES HERE

When a property name appears more than once in the prototype chain, the lowest property value on the chain is used (as we saw earlier with the description property). The lower property is said to override the higher property. For example, the Object class defines a toString( ) method on Object.prototype. We can override that version of toString( ) for our Book class by defining toString( ) on Book.prototype:

Book.prototype.toString = function ( ) {   return this.description + ", by " + this.getAuthor( ); }     trace(asGuide.toString( )); // Displays: "A book about ActionScript, by Colin Moock"    // But if we remove Book.prototype.toString,    // Displays: [object Object]    // which is the return of Object.prototype.toString( ).

Now recall the syntax used to assign the superclass of a class:

// Make Book a subclass of Product Book.prototype = new Product( );

Let's walk through how this statement establishes inheritance between Book and Product. First, we know that all Book instances have all the properties defined on Book.prototype. We also know that, by default, Book.prototype is a generic instance of the Object class. Assigning Product as the superclass for Book, using this approach, replaces the generic instance in Book.prototype with a completely new instance of Product. Hence, all properties defined by Product become defined on Book.prototype and therefore become available to all Book instances. With a Product instance stored in Book.prototype, the prototype chain for our asGuide Book instance becomes:

Object.prototype      // If not found here, return undefined Product.prototype     // If not found here, look on Object.prototype Book.prototype        // If not found here, look on Product.prototype asGuide               // If not found here, look on Book.prototype                       // INTERPRETER STARTS LOOKING FOR PROPERTIES HERE

The prototype chain can include any number of levels. The Product class can itself have a superclass that hands down properties to all of its subclasses.

If you're dying to know whether an object defines its own property or inherits a property of the same name via the prototype chain, you can use the Object.hasOwnProperty( ) method, described in the Language Reference. For example:

trace(asGuide.hasOwnProperty("author"));     // true trace(asGuide.hasOwnProperty("getAuthor"));  // false                                               // (because getAuthor is defined                                               // on Book.prototype, not asGuide)

Conversely, if you want to access a property on the prototype chain that is overridden, you can use the _ _proto_ _ property (discussed next) to refer explicitly to the prototype property of an object's class. For example, the following code shows how to access Book.prototype.description through a particular Book instance, asGuide:

trace(asGuide._ _proto_ _.description);

12.7.1 The _ _proto_ _ Property

We saw earlier in this chapter that the instanceof operator tells us whether an instance belongs to a particular class. But how does the interpreter know an individual object's class? Or, more specifically, how does the interpreter know which constructor's prototype to access when searching for an object property? While that kind of internal information normally is hidden in object-oriented languages, ActionScript exposes it to the developer.

When any object is created, the interpreter automatically assigns it a special property called _ _proto_ _ (note the two underscores on either side of the word proto). Then, into _ _proto_ _, the interpreter copies a reference to the object's class's prototype property. For example, when we create an instance of Book called asGuide, asGuide._ _proto_ _ is set to Book.prototype:

var asGuide = new Book("Colin Moock", "A book about ActionScript"); trace(asGuide._ _proto_ _ =  = Book.prototype);  // Displays: true

Primarily, the _ _proto_ _ property is used internally by the ActionScript interpreter. However, we can use it to determine the class of an object (as shown earlier in Example 12-5) or to access overridden properties. Although _ _proto_ _ is writable, it should not be tampered with in most situations. However, soon we'll encounter a special case that uses _ _proto_ _ as part of an alternative inheritance system.

12.7.2 The constructor Property

When a constructor's prototype object is created, the interpreter automatically assigns it a special property called constructor. The constructor property is a reference to the class constructor function on which the prototype object is defined. For example, the following equality test checks whether Book and Book.prototype.constructor refer to the same function:

trace(Book.prototype.constructor =  = Book);  // Displays: true

Note that the constructor property contains a reference to a constructor function, not a string representing the function's name. The constructor can be used to invoke a constructor function on an object in order to reinitialize it. The following example invokes the Book constructor on asGuide:

asGuide.constructor.call(asGuide,                           "Colin Moock",                          "A large book about ActionScript");

See Function.call( ) in the Language Reference for details.

The constructor property can also be used to access static properties and methods from an instance. For example:

function Box ( ) {   // Constructor code goes here } Box.numSides = 4;     var bigBox = new Box( ); trace(bigBox.constructor.numSides);  // Displays: 4

In ECMA-262, JavaScript, and ActionScript, the constructor property is written to a class's prototype (as Class.prototype.constructor), where instances of the class can retrieve it via the prototype chain. However, ActionScript also adds a constructor property directly to instances (as instanceName.constructor) effectively obscuring Class.prototype.constructor. ActionScript's nonstandard implementation is considered a bug by Macromedia the ECMA-262 standard requires that an instance retrieve its constructor via Class.prototype.constructor. In a future version of Flash, Macromedia will likely fix this bug (that is, in the future constructor will no longer be written directly to instances). We'll see how writing a constructor property to individual instances affects inheritance in the next section.

12.7.3 Advanced Issues with Standard SuperClass Assignment

As we've seen, the standard means for establishing a class's superclass is:

SubClass.prototype = new SuperClass( );

When using the new operator to establish inheritance, some developers have encountered minor problems, discussed in detail next. To sidestep these problems, some developers use the following unofficial technique, so-called _ _proto_ _-based inheritance, to establish a class's superclass:

SubClass.prototype._ _proto_ _ = SuperClass.prototype;

Here's how _ _proto_ _-based inheritance works. We know that instances of SubClass have access to the properties and methods defined on SubClass.prototype. Further, we know that SubClass.prototype is itself an object that accesses the properties and methods of its own class via the _ _proto_ _ property (remember the prototype chain?). Hence, _ _proto_ _-based inheritance says, "Give the SubClass.prototype object access to the properties and methods defined on SuperClass.prototype (via SubClass.prototype._ _proto_ _)."

Macromedia does not recommend using _ _proto_ _ instead of the new operator to establish inheritance. However, establishing inheritance with _ _proto_ _ is a relatively common advanced workaround that addresses the following issues.

12.7.3.1 Issue 1: Overwriting of prototype.constructor

Given a constructor function, SubClass, the property SubClass.prototype.constructor initially stores a reference to SubClass. What happens when you attempt to access the constructor property of an instance of SubClass as instanceName.constructor? In ECMA-262, constructor is not defined on the instance, so the reference is resolved via the prototype chain, and the value of SubClass.prototype.constructor is returned. In ActionScript, however, constructor is defined on the instance itself, so instanceName.constructor is returned, and the interpreter doesn't look any further up the prototype chain. Let's see this in an example:

function Ball ( ) {   // Class definition goes here } b = new Ball( ); trace(b.constructor); 

In ECMA-262, b.constructor refers to the Ball( ) constructor function as stored in Ball.prototype.constructor. In ActionScript for Flash Player 5 and Flash Player 6, b.constructor also refers to the Ball( ) constructor function, but this time, the value is read from the constructor property attached directly to b.

For this simple example, b.constructor ultimately resolves to the same value in both ECMA-262 and ActionScript. So does it matter that ECMA-262 reads the constructor property from the class's prototype, whereas ActionScript reads it from each separate instance? The answer lies in whether the two constructor properties are the same when constructor is accessed. In the simplest case, where an instance is created from a class, the discrepancy between ECMA-262 and ActionScript is indeed moot because the instance's constructor value matches its class's prototype.constructor property. However, when a superclass is established for a subclass using standard inheritance syntax (SubClass.prototype = new SuperClass( );), the subclass's previous prototype property is entirely replaced with a new SuperClass instance. Hence, any existing properties attached to SubClass.prototype, including constructor, are lost; conversely, all properties of the new SuperClass instance are added. Therefore, after an instance of SuperClass is assigned to SubClass.prototype, SubClass.prototype.constructor refers to the SuperClass constructor function, not the SubClass constructor as it once did.

The following code demonstrates this issue:

// Create SubClass function SubClass ( ) {    // Class definition goes here }     // Check subclass constructor before inheritance is established trace(SubClass.prototype.constructor =  = SubClass);    // Displays: true     // Create SuperClass function SuperClass ( ) {    // Class definition goes here }     // Set inheritance using standard syntax (with the new operator) SubClass.prototype = new SuperClass( );     // Check SubClass.prototype.constructor after inheritance is established trace(SubClass.prototype.constructor =  = SubClass);    // Now displays: false! trace(SubClass.prototype.constructor =  = SuperClass);  // Now displays: true!

Note that SubClass.prototype.constructor matches the SuperClass class constructor function instead of the SubClass class constructor function. What are the ramifications of this apparent discrepancy? And is ActionScript's departure from the ECMA-262 standard still moot?

Recall that in JavaScript and ECMA-262, SubClass instances retrieve the value of constructor from SubClass.prototype.constructor, so SubClass instances report the SuperClass's constructor instead of their own SubClass constructor. This is the intentional, albeit confusing, implementation of the ECMA-262 standard and is not considered a bug.

Recall also that in Flash Player 5 and Flash Player 6, ActionScript deviates from the ECMA-262 standard and writes the constructor property directly to each instance. Hence, in Flash Player 5 and Flash Player 6, anyInstance.constructor always refers to the function that created anyInstance, in this case SubClass, even after superclass inheritance is established. For example:

subInstance = new SubClass(); trace(subInstance.constructor == SuperClass);  // false in Flash 5 and 6 trace(subInstance.constructor == SubClass);    // true in Flash 5 and 6 // Note that these equality tests always return true in Flash Player 5 due   // to an unrelated bug in the == operator (which was fixed in Flash Player 6).

Because it is obscured by subInstance.constructor, ActionScript programmers wouldn't ordinarily notice the misleading SubClass.prototype.constructor property at all. Therefore the issue is somewhat moot in ActionScript for Flash Player 5 and 6. However, you should not rely on the constructor property that Flash 5 and 6 attach directly to instances, because it is expected to disappear from future versions of the Flash Player.

Some readers might be tempted to "fix" the quirky ECMA-262 implementation by reassigning SubClass.prototype.constructor so that it refers to the original SubClass constructor function, as follows:

SubClass.prototype.constructor = SubClass;

However, Macromedia recommends against directly modifying SubClass.prototype.constructor, because doing so alters the intended (albeit, perhaps unexpected) ECMA-compliant value of the property. If you need an accurate and consistent reference to an instance's constructor function, consider adding a custom property that stores a reference to the constructor (add the property after superclass assignment):

// Assign SuperClass SubClass.prototype = new SuperClass( ); // Create custom constructorFunc property SubClass.prototype.constructorFunc = SubClass;

If you are using the constructor property merely to determine whether an object belongs to a given class, use the instanceof operator instead. The instanceof operator returns true when the specified instance belongs to the specified class or any of its subclasses. The instanceof operator works as expected, regardless of whether inheritance is establish with _ _proto_ _ or with the new operator, as shown here:

SubClass.prototype = new SuperClass(); var subInstance = new SubClass(); trace(subInstance instanceof SuperClass);  // Displays: true trace(subInstance instanceof SubClass);    // Displays: true
12.7.3.2 Issue 2: Undesirable SuperClass constructor invocation

When the following statement is executed, the SuperClass function executes:

SubClass.prototype = new SuperClass( );

This extraneous function execution needlessly consumes resources and might perform unwanted tasks in the SuperClass constructor (such as attaching a movie clip or incrementing an instance counter). To prevent unwanted code execution, use a parameter that indicates if the constructor is being called merely to establish inheritance. For example:

function SuperClass ( ) {   // If the first argument is an object with a noConstruct property set to true...   if (arguments[0].noConstruct =  =  = true) {     // ...abort construction     return;   }       // Do regular construction } SubClass.prototype = new SuperClass( {noConstruct:true} );

In some cases, it may be satisfactory to simply check the number of arguments that were passed to the SuperClass constructor function. Here, the function expects two arguments; it exits if no arguments are passed in, as is typical when using new to establish inheritance:

function SuperClass (param1, param2) {   // If no arguments were passed...   if (arguments.length =  = 0) {     // ...abort construction     return;   }       // Do regular construction } SubClass.prototype = new SuperClass( );

In the alternative style of inheritance, based on _ _proto_ _:

SubClass.prototype._ _proto_ _ = SuperClass.prototype;

the SuperClass.prototype object is assigned directly to SubClass.prototype._ _proto_ _, and the SuperClass constructor does not execute.

12.7.3.3 Issue 3: Undefined properties appear in SubClass.prototype

A superclass constructor normally assigns properties to the instances it creates. In this example, the constructor SuperClass assigns instances a property, p:

function SuperClass (p) {   this.p = p; }

When inheritance is established with the standard new syntax, an instance of SuperClass is assigned to SubClass.prototype, and the property p is created on SubClass.prototype with a value of undefined (because a value for p was not passed to the SuperClass constructor). Hence, the undefined property p appears in for-in loops executed on all SubClass instances. For example:

// SubClass constructor function SubClass (p) {   // Call superclass constructor   super(p); }     // SuperClass constructor function SuperClass (p) {   this.p = p; }     // Establish inheritance SubClass.prototype = new SuperClass( );     // Create an instance of SubClass with no value for p subInstance = new SubClass( );     // Check subInstance's properties. The property p shows up // even though it was not assigned a value when constructed. for (var prop in subInstance) {   // Displays: Property p is: undefined   trace("Property " + prop + " is: " + typeof subInstance[prop]); }

Though bothersome to some developers, the appearance of p on all instances of SubClass is normal and expected behavior. When a class or superclass defines a property, that property is expected to be accessible on all instances, whether or not it contains useful data. However, if preferred, we can prevent undefined properties from being assigned to instances as follows:

function SuperClass (p) {   // Create p only if it is not undefined   if (p != undefined) this.p = p; }

That's not necessarily good OOP practice, but it does the job of hiding p when it has no value (again, as would be typical if the constructor was called to due a superclass assignment).

12.7.3.4 A custom setSuperClass( ) function

If you decide to use _ _proto_ _ to establish inheritance (despite Macromedia's recommendations against it), consider creating a custom function that hides the slightly obtuse _ _proto_ _ syntax. Example 12-6 shows a custom setSuperClass( ) function that assigns a superclass to a subclass (i.e., establishes inheritance) using _ _proto_ _.

Example 12-6. A setSuperClass( ) function
// Add setSuperClass( ) to Function.prototype so it's // available to all function objects Function.prototype.setSuperClass = function (theSuperClass) {   // Establish the super/sub class relationship.   this.prototype._ _proto_ _ = theSuperClass.prototype; }     // Create a superclass  function Circle (radius) {    this.r = radius; }     // Create a subclass function Ball (radius) {    // Can't use super when establishing inheritance with _ _proto_ _   this.base = Circle;   this.base(radius);   delete this.base; }     // Now establish inheritance with our custom setSuperClass( ) function Ball.setSuperClass(Circle);     // Create an instance of Ball var theBall = new Ball(15);     // Check the property assigned by the Circle class trace(theBall.r);  // Displays: 15

Note that we might have naturally used the name "extends" instead of "setSuperClass" (following Java terminology), but "extends" is reserved as a potential keyword.

A word of warning to _ _proto_ _ users: when inheritance is established via prototype._ _proto_ _, Flash Player 6's super operator cannot be used to invoke a superclass constructor. The superclass constructor must be invoked with the following older style syntax if inheritance was established using _ _proto_ _:

function SubClass (someArg) {   this.base = SuperClass;   this.base(someArg);   delete this.base; }

However, after establishing inheritance via _ _proto_ _, the super operator can still be used to invoke a superclass's methods. For example:

SubClass.prototype.hello = function ( ) {   super.hello( );  // Works even when inheritance is                   // established through prototype._ _proto_ _ }   

12.7.4 Is There a Test?

While the preceding information about prototype, constructor, and _ _proto_ _ is perhaps interesting to programming language enthusiasts, one might still wonder, must I really know it all? Does it change my use of classes and objects?

Quite frankly, no, you should not have to know this information. The inner workings of a language's OOP implementation are not typically the concern of the language's users. It's not normal for users to concoct their own OOP system, nor should developing an OOP system be the user's responsibility. However, ECMAScript (the specification upon which ActionScript and JavaScript are based) was originally designed as a lightweight scripting language. The current demands put upon it have in some ways outstripped its expected purpose. Hence, ECMAScript developers have been forced to take matters into their own hands, attempting to fix problems inherent in the language implementation, in order to achieve their goals. Meanwhile, work is underway to completely revise the inheritance system in ECMAScript 4 and JavaScript 2.0. See:

http://www.mozilla.org/js/language/js20
http://www.mozilla.org/js/language/es4

At the time of this writing, the ECMAScript 4 specification was very much under development, so Macromedia's position on the future of ECMAScript remains to be seen.

In any case, the only time you should concern yourself with prototype and _ _proto_ _ is when you hit a real limitation in your work. Unless the standard inheritance system in ActionScript causes problems for your programs, you should not bother with _ _proto_ _; however, if you encounter problems caused by standard inheritance, investigate alternatives. Luckily, other developers have paved some of the way for you. See Dave Yang's information for one perspective:

http://www.quantumwave.com/flash/inheritance.html

Branden Hall and Robert Penner have also written about inheritance issues (see their books, listed in Appendix A). For the most part, however, you can simply use the standard means of establishing inheritance (assigning a new instance of a superclass to the subclass's prototype property) without worrying about the technicalities of the prototype system.

     



    ActionScript for Flash MX. The Definitive Guide
    ActionScript for Flash MX: The Definitive Guide, Second Edition
    ISBN: 059600396X
    EAN: 2147483647
    Year: 2002
    Pages: 780
    Authors: Colin Moock

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