Section 9.5. Superclasses and Subclasses


9.5. Superclasses and Subclasses

Java, C++, and other class-based object-oriented languages have an explicit concept of the class hierarchy. Every class can have a superclass from which it inherits properties and methods. Any class can be extended, or subclassed, so that the resulting subclass inherits its behavior. As shown previously, JavaScript supports prototype inheritance instead of class-based inheritance. Still, JavaScript analogies to the class hierarchy can be drawn. In JavaScript, the Object class is the most generic, and all other classes are specialized versions, or subclasses, of it. Another way to say this is that Object is the superclass of all the built-in classes, and all classes inherit a few basic methods from Object.

Recall that objects inherit properties from the prototype object of their constructor. How do they also inherit properties from the Object class? Remember that the prototype object is itself an object; it is created with the Object( ) constructor. This means the prototype object itself inherits properties from Object.prototype! Prototype-based inheritance is not limited to a single prototype object; instead, a chain of prototype objects is involved. Thus, a Complex object inherits properties from Complex.prototype and from Object.prototype. When you look up a property in a Complex object, the object itself is searched first. If the property is not found, the Complex.prototype object is searched next. Finally, if the property is not found in that object, the Object.prototype object is searched.

Note that because the Complex prototype object is searched before the Object prototype object, properties of Complex.prototype hide any properties with the same name in Object.prototype. For example, in the Complex class of Example 9-2, a toString( ) method was defined in the Complex.prototype object. Object.prototype also defines a method with this name, but Complex objects never see it because the definition of toString( ) in Complex.prototype is found first.

The classes shown so far in this chapter are all direct subclasses of Object. When necessary, however, it is possible to subclass any other class. Recall the Rectangle class shown earlier in the chapter, for example. It had properties that represent the width and height of the rectangle but no properties describing its position. Suppose you want to create a subclass of Rectangle in order to add fields and methods related to the position of the rectangle. To do this, simply make sure that the prototype object of the new class is itself an instance of Rectangle so that it inherits all the properties of Rectangle.prototype. Example 9-3 repeats the definition of a simple Rectangle class and then extends it to define a PositionedRectangle class.

Example 9-3. Subclassing a JavaScript class

 // Here is a simple Rectangle class. // It has a width and height and can compute its own area function Rectangle(w, h) {     this.width = w;     this.height = h; } Rectangle.prototype.area = function( ) { return this.width * this.height; } // Here is how we might subclass it function PositionedRectangle(x, y, w, h) {     // First, invoke the superclass constructor on the new object     // so that it can initialize the width and height.     // We use the call method so that we invoke the constructor as a     // method of the object to be initialized.     // This is called constructor chaining.     Rectangle.call(this, w, h);     // Now store the position of the upper-left corner of the rectangle     this.x = x;     this.y = y; } // If we use the default prototype object that is created when we // define the PositionedRectangle( ) constructor, we get a subclass of Object. // To subclass Rectangle, we must explicitly create our prototype object. PositionedRectangle.prototype = new Rectangle( ); // We create this prototype object for inheritance purposes, but we // don't actually want to inherit the width and height properties that // each Rectangle object has, so delete them from the prototype. delete PositionedRectangle.prototype.width; delete PositionedRectangle.prototype.height; // Since the prototype object was created with the Rectangle( ) constructor, // it has a constructor property that refers to that constructor.  But // we want PositionedRectangle objects to have a different constructor // property, so we've got to reassign this default constructor property. PositionedRectangle.prototype.constructor = PositionedRectangle; // Now that we've configured the prototype object for our subclass, // we can add instance methods to it. PositionedRectangle.prototype.contains = function(x,y) {     return (x > this.x && x < this.x + this.width &&             y > this.y && y < this.y + this.height); } 

As you can see from Example 9-3, creating a subclass in JavaScript is not as simple as creating a class that inherits directly from Object. First, there is the issue of invoking the superclass constructor from the subclass constructor. Take care when you do this that the superclass constructor is invoked as a method of the newly created object. Next, there are the tricks required to set the prototype object of the subclass constructor. You must explicitly create this prototype object as an instance of the superclass, then explicitly set the constructor property of the prototype object.[*] Optionally, you may also want to delete any properties that the superclass constructor created in the prototype object because what's important are the properties that the prototype object inherits from its prototype.

[*] There is a bug in Rhino (the Java-based JavaScript interpreter) in version 1.6r1 and earlier that makes the constructor property read-only and nondeletable. In these versions of Rhino, the code to set the constructor property of the prototype object fails silently, and instances of the PositionedRectangle class inherit a constructor property that refers to the Rectangle( ) constructor. In practice, this bug is not severe because property inheritance works correctly, and the instanceof operator also works correctly for Rectangle and PositionedRectangle objects.

Having defined this PositionedRectangle class, you can use it with code like this:

 var r = new PositionedRectangle(2,2,2,2); print(r.contains(3,3));  // invoke an instance method print(r.area( ));         // invoke an inherited instance method // Use the instance fields of the class: print(r.x + ", " + r.y + ", " + r.width + ", " + r.height); // Our object is an instance of all 3 of these classes print(r instanceof PositionedRectangle &&       r instanceof Rectangle &&       r instanceof Object); 

9.5.1. Constructor Chaining

In the example just shown, the PositionedRectangle( ) constructor function needed to explicitly invoke the superclass constructor function. This is called constructor chaining and is quite common when creating subclasses. You can simplify the syntax for constructor chaining by adding a property named superclass to the prototype object of the subclass:

 // Store a reference to our superclass constructor. PositionedRectangle.prototype.superclass = Rectangle; 

With this property defined, the syntax for constructor chaining is simpler:

 function PositionedRectangle(x, y, w, h) {     this.superclass(w,h);     this.x = x;     this.y = y; } 

Note that the superclass constructor function is explicitly invoked through the this object. This means that you no longer need to use call( ) or apply( ) to invoke the superclass constructor as a method of that object.

9.5.2. Invoking Overridden Methods

When a subclass defines a method that has the same name as a method in the superclass, the subclass overrides that method. This is a relatively common thing to do when creating subclasses of existing classes. Anytime you define a toString( ) method for a class, you override the toString( ) method of Object, for example.

A method that overrides another often wants to augment the functionality of the overridden method instead of replacing it altogether. To do that, a method must be able to invoke the method that it overrides. In a sense, this is a kind of method chaining, as described for constructors earlier in this chapter. Invoking an overridden method is more awkward than invoking a superclass constructor, however.

Let's consider an example. Suppose the Rectangle class had defined a toString( ) method (as it should have in the first place):

 Rectangle.prototype.toString = function( ) {     return "[" + this.width + "," + this.height + "]"; } 

If you give Rectangle a toString( ) method, you really must override that method in PositionedRectangle so that instances of the subclass have a string representation that reflects all their properties, not just their width and height properties. PositionedRectangle is a simple enough class that its toString( ) method can just return the values of all properties. But for the sake of example, let's handle the position properties and delegate to its superclass for the width and height properties. Here is what the code might look like:

 PositionedRectangle.prototype.toString = function( ) {     return "(" + this.x + "," + this.y + ") " +      // our fields           Rectangle.prototype.toString.apply(this);  // chain to superclass } 

The superclass's implementation of toString( ) is a property of the superclass's prototype object. Note that you can't invoke it directly. Invoke it with apply( ) so that you can specify the object on which it should be called.

If you add a superclass property to PositionedRectangle.prototype, this code can be rewritten in a superclass-independent way:

 PositionedRectangle.prototype.toString = function( ) {     return "(" + this.x + "," + this.y + ") " +      // our fields           this.superclass.prototype.toString.apply(this); } 




JavaScript. The Definitive Guide
JavaScript: The Definitive Guide
ISBN: 0596101996
EAN: 2147483647
Year: 2004
Pages: 767

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