DekGenius.com
Team LiB   Previous Section   Next Section

8.4 Prototypes and Inheritance

We've seen how inefficient it can be to use a constructor to assign methods to the objects it initializes. When we do this, each and every object created by the constructor has identical copies of the same method properties. There is a much more efficient way to specify methods, constants, and other properties that are shared by all objects in a class.

JavaScript objects "inherit" properties from a prototype object.[1] Every object has a prototype; all of the properties of the prototype object appear to be properties of any objects for which it is a prototype. That is, each object inherits properties from its prototype.

[1] Prototypes were introduced in JavaScript 1.1; they are not supported in the now obsolete JavaScript 1.0.

The prototype of an object is defined by the constructor function that was used to create and initialize the object. All functions in JavaScript have a prototype property that refers to an object. This prototype object is initially empty, but any properties you define in it will be inherited by all objects created by the constructor.

A constructor defines a class of objects and initializes properties, such as width and height, that are the state variables for the class. The prototype object is associated with the constructor, so each member of the class inherits exactly the same set of properties from the prototype. This means that the prototype object is an ideal place for methods and other constant properties.

Note that inheritance occurs automatically, as part of the process of looking up a property value. Properties are not copied from the prototype object into new objects; they merely appear as if they were properties of those objects. This has two important implications. First, the use of prototype objects can dramatically decrease the amount of memory required by each object, since the object can inherit many of its properties. The second implication is that an object inherits properties even if they are added to its prototype after the object is created.

Each class has one prototype object, with one set of properties. But there are potentially many instances of a class, each of which inherits those prototype properties. Because one prototype property can be inherited by many objects, JavaScript must enforce a fundamental asymmetry between reading and writing property values. When you read property p of an object o, JavaScript first checks to see if o has a property named p. If it does not, it next checks to see if the prototype object of o has a property named p. This is what makes prototype-based inheritance work.

When you write the value of a property, on the other hand, JavaScript does not use the prototype object. To see why, consider what would happen if it did: suppose you try to set the value of the property o.p when the object o does not have a property named p. Further suppose that JavaScript goes ahead and looks up the property p in the prototype object of o and allows you to set the property of the prototype. Now you have changed the value of p for a whole class of objects -- not at all what you intended.

Therefore, property inheritance occurs only when you read property values, not when you write them. If you set the property p in an object o that inherits that property from its prototype, what happens is that you create a new property p directly in o. Now that o has its own property named p, it no longer inherits the value of p from its prototype. When you read the value of p, JavaScript first looks at the properties of o. Since it finds p defined in o, it doesn't need to search the prototype object and never finds the value of p defined there. We sometimes say that the property p in o "shadows" or "hides" the property p in the prototype object. Prototype inheritance can be a confusing topic. Figure 8-1 illustrates the concepts we've discussed here.

Figure 8-1. Objects and prototypes
figs/js4_0801.gif

Because prototype properties are shared by all objects of a class, it generally makes sense to use them only to define properties that are the same for all objects within the class. This makes prototypes 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 its default value in a prototype object. Then, the few objects that want to deviate from the default value can create their own private, unshared copies of the property and define their own nondefault values.

Let's move from an abstract discussion of prototype inheritance to a concrete example. Suppose we define a Circle( ) constructor function to create objects that represent circles. The prototype object for this class is Circle.prototype,[2] so we can define a constant available to all Circle objects like this:

[2] The prototype object of a constructor is created automatically by JavaScript. In most versions of JavaScript, every function is automatically given an empty prototype object, just in case it is used as a constructor. In JavaScript 1.1, however, the prototype object is not created until the function is used as a constructor for the first time. This means that if you require compatibility with JavaScript 1.1, you should create at least one object of a class before you use the prototype object to assign methods and constants to objects of that class. So, if we have defined a Circle( ) constructor but have not yet used it to create any Circle objects, we'd define the constant property pi like this:

//First create and discard a dummy object; forces prototype object creation. new Circle ( ); //Now we can set properties in the prototype. Circle.prototype.pi = 3.14159'


Circle.prototype.pi = 3.14159; 

Example 8-4 shows our Circle example fully fleshed out. The code defines a Circle class by first defining a Circle( ) constructor to initialize each individual object and then setting properties of Circle.prototype to define methods and constants shared by all instances of the class.

Example 8-4. Defining a Circle 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.
// This forces the prototype object to be created in JavaScript 1.1.
new Circle(0,0,0);

// 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 instruction.
Circle.prototype.pi = 3.14159;

// Define a method to compute the circumference of the circle.
// First declare a function, then assign it to a prototype property.
// Note the use of the constant defined above.
function Circle_circumference(  ) { return 2 * this.pi * this.r; }
Circle.prototype.circumference = Circle_circumference;

// Define another method. This time we use a function literal to define
// the function and assign it to a prototype property all in one step.
Circle.prototype.area = function(  ) { return this.pi * this.r * this.r; }

// The Circle class is defined.
// Now we can create an instance and invoke its methods.
var c = new Circle(0.0, 0.0, 1.0);
var a = c.area(  );
var p = c.circumference(  );

8.4.1 Prototypes and Built-in Classes

It is not only user-defined classes that have prototype objects. Built-in classes, such as String and Date, have prototype objects too, and you can assign values to them.[3] For example, the following code defines a new method that is available for all String objects:

[3] In JavaScript 1.1 and later.

// Returns true if the last character is c
String.prototype.endsWith = function(c) {
    return (c == this.charAt(this.length-1))
} 

Having defined the new endsWith( ) method in the String prototype object, we can use it like this:

var message = "hello world";
message.endsWith('h')  // Returns false
message.endsWith('d')  // Returns true 
    Team LiB   Previous Section   Next Section