JavaScript: The Definitive Guide

Previous Chapter 7
Objects
Next
 

7.4 Object Prototypes

We've seen that a constructor function defines a "class" of objects in JavaScript--all objects created with a given constructor will be initialized in the same way and will therefore have the same set of properties. These properties may include methods, for (as we've also seen) you can use a constructor function to assign a set of methods to each object that is a member of the class.

In Navigator 3.0 and Internet Explorer 3.0, there is another way to specify the methods, constants, and other properties that all objects in a class will support. The technique is to define the methods and other properties in a prototype object for the class. A prototype object is a special object, associated with the constructor function for a class, that has a very important feature: any properties defined by the prototype object of a class will appear as properties of every object of that class. This is true of properties that are added to the prototype both before and after the objects are defined. The properties of the prototype object of a class are shared by all objects of that class (i.e., objects do not get their own unique copy of the prototype properties, so memory usage is minimal).

The properties of the prototype object for a class can be read through all objects of the class, and, although they appear to be, they are not actually properties of those objects. There is a single copy of each prototype property, and this copy is shared by all objects in the class. When you read one of these properties of an object, you are reading that shared value from the prototype object. When you set the value of one of these properties for a particular object, on the other hand, you are actually creating a new property for that one object. From that point on, for that one particular object, the newly created property "shadows," or hides, the shared property in the prototype object. Figure 7.1 illustrates how a private, non-shared property can shadow a shared prototype property.

Figure 7.1: Objects and prototypes

[Graphic: Figure 7-1]

Because prototype properties are shared by all objects of a class, it only generally makes sense to use them to define properties that will be the same for all objects within the class. This makes them ideal for defining methods. Other properties with constant values (such as mathematical constants) are also suitable for definition with prototype properties. If your class defines a property with a very commonly used default value, you might define this property, and the default value in a prototype object. Then the few objects that want to deviate from the default value can create their own private, unshared, copy of the property, defining their own nondefault property value.

After all this discussion of how prototype objects and their properties work, we can now discuss where you can find prototype properties, and how they are created. The prototype object defines methods and other constant properties for a class of objects; classes of objects are defined by a common constructor; therefore, the prototype object should be associated with the constructor function. This is indeed the case. If we were to define a Circle() constructor function to create objects that represent circles, then the prototype object for this class would be Circle.prototype, and we could define a constant that would be available to all Circle objects like this:

Circle.prototype.pi = 3.14159;

The prototype object of a constructor is created automatically by JavaScript. In Navigator, it is created the first time the constructor is used with the new operator. What this means is that you must create at least one object of a class before you can use the prototype object to assign methods and constants to objects of that class. So, if we have defined a Circle() constructor, but not yet used it to create any Circle objects, we'd define the constant property pi like this:

// First create and discard a dummy Circle object.
// All this does is force the prototype object to be created.
new Circle();
// Now we can set properties in the prototype
Circle.prototype.pi = 3.14159;
This requirement that an object be created before the prototype object is available is an unfortunate blemish in the JavaScript language design. If you forget to create an object before using the prototype you'll get an error message indicating that the prototype object does not have the property you are trying to set (i.e., the object does not exist). It is an annoyance, but a minor one. In Internet Explorer, it is not necessary to create a dummy object to force the prototype object to be created; IE provides a prototype object for all JavaScript functions, whether they are used as constructors or not.

Prototype objects and their properties can be quite confusing. Figure 7.1 illustrates several of the important prototype concepts; you should study it carefully. In addition to the figure, Example 7.4 is a concrete example of how you can use prototypes to help you define a class of objects. In this example, we've switched from our Rectangle class to a new Circle class. The code defines a Circle class of objects, by first defining a Circle() constructor method to initialize each individual object, and then by setting properties on Circle.prototype to define methods, constants, and defaults shared by all instances of the class.

Example 7.4: Defining a Class with a Prototype Object

// Define a constructor method for our class.
// Use it to initialize properties that will be different for
// each individual circle object.
function Circle(x, y, r) 
{
    this.x = x;  // the X coordinate of the center of the  circle
    this.y = y;  // the Y coordinate of the center of the circle
    this.r = r;  // the radius of the circle
}
// Create and discard an initial Circle object.
// Doing this forces the prototype object to be created
new Circle(0,0,0);
// Now define a constant; a property that will be shared by
// all circle objects. Actually, we could just use Math.PI,
// but we do it this way for the sake of example.
Circle.prototype.pi = 3.14159;
// Now define some functions that perform computations on circles
// Note the use of the constant defined above
function Circle_circumference() { return 2 * this.pi * this.r; }
function Circle_area() { return this.pi * this.r * this.r; }
// Make these functions into methods of all Circle objects by
// setting them as properties of the prototype object.
Circle.prototype.circumference = Circle_circumference;
Circle.prototype.area = Circle_area;
// Now define a default property. Most Circle objects will share this 
// default value, but some may override it by setting creating their 
// own unshared copy of the property.
Circle.prototype.url = "images/default_circle.gif";
// Now, create a circle object, and use the methods defined
// by the prototype object
c = new Circle(0.0, 0.0, 1.0);
a = c.area();
p = c.circumference();

An important point to note about prototypes is that in Navigator 3.0, you can use them with built-in object types, not just those that you define yourself. For example, if you wrote a function that operated on a string object, you could assign it as a method to String.prototype, and make it accessible as a method of all JavaScript strings. This technique does not work in Internet Explorer 3.0. IE 3.0 does not support the prototypes for Boolean and Number objects, and the properties of String.prototype are only available to actual String objects, not primitive string values, as they are in Navigator. These shortcomings will be fixed in a future version of IE.

Finally, a couple of points to remember about prototypes are that they are not available in Navigator 2.0, and that prototype properties are shared by all objects of a given class, regardless of whether the prototype property is defined before or after any given object is created.


Previous Home Next
Methods Book Index Classes in JavaScript

HTML: The Definitive Guide CGI Programming JavaScript: The Definitive Guide Programming Perl WebMaster in a Nutshell