Inheritance - The Principles of Object-Oriented Javascript (2014)

The Principles of Object-Oriented Javascript (2014)

Chapter 5. Inheritance

Learning how to create objects is the first step to understanding object-oriented programming. The second step is to understand inheritance. In traditional object-oriented languages, classes inherit properties from other classes.

In JavaScript, however, inheritance can occur between objects with no classlike structure defining the relationship. The mechanism for this inheritance is one with which you are already familiar: prototypes.

Prototype Chaining and Object.prototype

JavaScript’s built-in approach for inheritance is called prototype chaining, or prototypal inheritance. As you learned in Chapter 4, prototype properties are automatically available on object instances, which is a form of inheritance. The object instances inherit properties from the prototype. Because the prototype is also an object, it has its own prototype and inherits properties from that. This is the prototype chain: An object inherits from its prototype, while that prototype in turn inherits from its prototype, and so on.

All objects, including those you define yourself, automatically inherit from Object unless you specify otherwise (discussed later in this chapter). More specifically, all objects inherit from Object.prototype. Any object defined via an object literal has its [[Prototype]] set toObject.prototype, meaning that it inherits properties from Object.prototype, just like book in this example:

var book = {

title: "The Principles of Object-Oriented JavaScript"

};

var prototype = Object.getPrototypeOf(book);

console.log(prototype === Object.prototype); // true

Here, book has a prototype equal to Object.prototype. No additional code was necessary to make this happen, as this is the default behavior when new objects are created. This relationship means that book automatically receives methods from Object.prototype.

Methods Inherited from Object.prototype

Several of the methods used in the past couple of chapters are actually defined on Object.prototype and are therefore inherited by all other objects. Those methods are:

§ hasOwnProperty(). Determines whether an own property with the given name exists

§ propertyIsEnumerable(). Determines whether an own property is enumerable

§ isPrototypeOf(). Determines whether the object is the prototype of another

§ valueOf(). Returns the value representation of the object

§ toString(). Returns a string representation of the object

These five methods appear on all objects through inheritance. The last two are important when you need to make objects work consistently in JavaScript, and sometimes you might want to define them yourself.

valueOf()

The valueOf() method gets called whenever an operator is used on an object. By default, valueOf() simply returns the object instance. The primitive wrapper types override valueOf() so that it returns a string for String, a Boolean for Boolean, and a number for Number. Likewise, the Date object’s valueOf() method returns the epoch time in milliseconds (just as Date.prototype.getTime() does). This is what allows you to write code that compares dates such as:

var now = new Date();

var earlier = new Date(2010, 1, 1);

❶ console.log(now > earlier); // true

In this example, now is a Date representing the current time, and earlier is a fixed date in the past. When the greater-than operator (>) is used ❶, the valueOf() method is called on both objects before the comparison is performed. You can even subtract one date from another and get the difference in epoch time because of valueOf().

You can always define your own valueOf() method if your objects are intended to be used with operators. If you do define a valueOf() method, keep in mind that you’re not changing how the operator works, only what value is used with the operator’s default behavior.

toString()

The toString() method is called as a fallback whenever valueOf() returns a reference value instead of a primitive value. It is also implicitly called on primitive values whenever JavaScript is expecting a string. For example, when a string is used as one operand for the plus operator, the other operand is automatically converted to a string. If the other operand is a primitive value, it is converted into a string representation (for example, true becomes "true"), but if it is a reference value, then valueOf() is called. If valueOf() returns a reference value, toString() is called and the returned value is used. For example:

var book = {

title: "The Principles of Object-Oriented JavaScript"

};

var message = "Book = " + book;

console.log(message); // "Book = [object Object]"

This code constructs the string by combining "Book = " with book. Since book is an object, its toString() method is called. That method is inherited from Object.prototype and returns the default value of "[object Object]" in most JavaScript engines. If you are happy with that value, there’s no need to change your object’s toString() method. Sometimes, however, it’s useful to define your own toString() method so that string conversions return a value that gives more information. Suppose, for example, that you want the previous script to log the book’s title:

var book = {

title: "The Principles of Object-Oriented JavaScript",

toString: function() {

return "[Book " + this.title + "]"

}

};

var message = "Book = " + book;

// "Book = [Book The Principles of Object-Oriented JavaScript]"

❶ console.log(message);

This code defines a custom toString() method for book that returns a more useful value ❶ than the inherited version. You don’t usually need to worry about defining a custom toString() method, but it’s good to know that it’s possible to do so if necessary.

Modifying Object.prototype

All objects inherit from Object.prototype by default, so changes to Object.prototype affect all objects. That’s a very dangerous situation. You were advised in Chapter 4 not to modify built-in object prototypes, and that advice goes double for Object.prototype. Take a look at what can happen:

Object.prototype.add = function(value) {

return this + value;

};

var book = {

title: "The Principles of Object-Oriented JavaScript"

};

console.log(book.add(5)); // "[object Object]5"

console.log("title".add("end")); // "titleend"

// in a web browser

console.log(document.add(true)); // "[object HTMLDocument]true"

console.log(window.add(5)); // "[object Window]true"

Adding Object.prototype.add() causes all objects to have an add() method, whether or not it actually makes sense. This problem has been an issue not just for developers but also for the committee that works on the JavaScript language: It has had to put new methods in different locations because adding methods to Object.prototype can have unforeseen consequences.

Another aspect of this problem involves adding enumerable properties to Object.prototype. In the previous example, Object.prototype.add() is an enumerable property, which means it will show up when you use a for-in loop, such as:

var empty = {};

for (var property in empty) {

console.log(property);

}

Here, an empty object will still output "add" as a property because it exists on the prototype and is enumerable. Given how often the for-in construct is used in JavaScript, modifying Object.prototype with enumerable properties has the potential to affect a lot of code. For this reason, Douglas Crockford recommends using hasOwnProperty() in for-in loops all the time,[1] such as:

var empty = {};

for (var property in empty) {

if (empty.hasOwnProperty(property)) {

console.log(property);

}

}

While this approach is effective against possible unwanted prototype properties, it also limits the use of for-in to only own properties, which may or may not be want you want. Your best bet for the most flexibility is to not modify Object.prototype.

Object Inheritance

The simplest type of inheritance is between objects. All you have to do is specify what object should be the new object’s [[Prototype]]. Object literals have Object.prototype set as their [[Prototype]] implicitly, but you can also explicitly specify [[Prototype]] with theObject.create() method.

The Object.create() method accepts two arguments. The first argument is the object to use for [[Prototype]] in the new object. The optional second argument is an object of property descriptors in the same format used by Object.defineProperties() (see Chapter 3). Consider the following:

var book = {

title: "The Principles of Object-Oriented JavaScript"

};

// is the same as

var book = Object.create(Object.prototype, {

title: {

configurable: true,

enumerable: true,

value: "The Principles of Object-Oriented JavaScript",

writable: true

}

});

The two declarations in this code are effectively the same. The first declaration uses an object literal to define an object with a single property called title. That object automatically inherits from Object.prototype, and the property is set to be configurable, enumerable, and writable by default. The second declaration takes the same steps but does so explicitly using Object.create(). The resulting book object from each declaration behaves the exact same way. But you’ll probably never write code that inherits from Object.prototype directly, because you get that by default. Inheriting from other objects is much more interesting:

var person1 = {

name: "Nicholas",

sayName: function() {

console.log(this.name);

}

};

var person2 = Object.create(person1, {

name: {

configurable: true,

enumerable: true,

value: "Greg",

writable: true

}

});

person1.sayName(); // outputs "Nicholas"

person2.sayName(); // outputs "Greg"

console.log(person1.hasOwnProperty("sayName")); // true

console.log(person1.isPrototypeOf(person2)); // true

console.log(person2.hasOwnProperty("sayName")); // false

This code creates an object, person1, with a name property and a sayName() method. The person2 object inherits from person1, so it inherits both name and sayName(). However, person2 is defined via Object.create(), which also defines an own name property for person2. This own property shadows the prototype property of the same name and is used in its place. So, person1.sayName() outputs "Nicholas", while person2.sayName() outputs "Greg". Keep in mind that sayName() still exists only on person1 and is being inherited by person2.

The inheritance chain in this example is longer for person2 than it is for person1. The person2 object inherits from the person1 object, and the person1 object inherits from Object.prototype. See Figure 5-1.

The prototype chain for person2 includes person1 and Object.prototype.

Figure 5-1. The prototype chain for person2 includes person1 and Object.prototype.

When a property is accessed on an object, the JavaScript engine goes through a search process. If the property is found on the instance (that is, if it’s an own property), that property value is used. If the property is not found on the instance, the search continues on [[Prototype]]. If the property is still not found, the search continues to that object’s [[Prototype]], and so on until the end of the chain is reached. That chain usually ends with Object.prototype, whose [[Prototype]] is set to null.

You can also create objects with a null [[Prototype]] via Object.create(), such as:

var nakedObject = Object.create(null);

console.log("toString" in nakedObject); // false

console.log("valueOf" in nakedObject); // false

The nakedObject in this example is an object with no prototype chain. That means built-in methods such as toString() and valueOf() aren’t present on the object. In effect, this object is a completely blank slate with no predefined properties, which makes it perfect for creating a lookup hash without potential naming collisions with inherited property names. There aren’t many other uses for an object like this, and you can’t use it as if it were inheriting from Object.prototype. For example, any time you use an operator on nakedObject, you’ll just get an error along the lines of “Cannot convert object to primitive value.” Still, it’s an interesting quirk of the JavaScript language that you can create a prototype-less object.

Constructor Inheritance

Object inheritance in JavaScript is also the basis of constructor inheritance. Recall from Chapter 4 that almost every function has a prototype property that can be modified or replaced. That prototype property is automatically assigned to be a new generic object that inherits fromObject.prototype and has a single own property called constructor. In effect, the JavaScript engine does the following for you:

// you write this

function YourConstructor() {

// initialization

}

// JavaScript engine does this for you behind the scenes

YourConstructor.prototype = Object.create(Object.prototype, {

constructor: {

configurable: true,

enumerable: true,

value: YourConstructor

writable: true

}

});

So without doing anything extra, this code sets the constructor’s prototype property to an object that inherits from Object.prototype, which means any instances of YourConstructor also inherit from Object.prototype. YourConstructor is a subtype of Object, and Objectis a supertype of YourConstructor.

Because the prototype property is writable, you can change the prototype chain by overwriting it. Consider the following example:

function Rectangle(length, width) {

this.length = length;

this.width = width;

}

Rectangle.prototype.getArea = function() {

return this.length * this.width;

};

Rectangle.prototype.toString = function() {

return "[Rectangle " + this.length + "x" + this.width + "]";

};

// inherits from Rectangle

function Square(size) {

this.length = size;

this.width = size;

}

Square.prototype = new Rectangle();

Square.prototype.constructor = Square;

Square.prototype.toString = function() {

return "[Square " + this.length + "x" + this.width + "]";

};

var rect = new Rectangle(5, 10);

var square = new Square(6);

console.log(rect.getArea()); // 50

console.log(square.getArea()); // 36

console.log(rect.toString()); // "[Rectangle 5x10]"

console.log(square.toString()); // "[Square 6x6]"

console.log(rect instanceof Rectangle); // true

console.log(rect instanceof Object); // true

console.log(square instanceof Square); // true

console.log(square instanceof Rectangle); // true

console.log(square instanceof Object); // true

In this code, there are two constructors: Rectangle ❶ and Square ❷. The Square constructor has its prototype property overwritten with an instance of Rectangle. No arguments are passed into Rectangle at this point because they don’t need to be used, and if they were, all instances of Square would share the same dimensions. To change the prototype chain this way, you always need to make sure that the constructor won’t throw an error if the arguments aren’t supplied (many constructors contain initialization logic that may require the arguments) and that the constructor isn’t altering any sort of global state, such as keeping track of how many instances have been created. The constructor property is restored on Square.prototype after the original value is overwritten.

After that, rect is created as an instance of Rectangle, and square is created as an instance of Square. Both objects have the getArea() method because it is inherited from Rectangle.prototype. The square variable is considered an instance of Square as well as Rectangle andObject because instanceof uses the prototype chain to determine the object type. See Figure 5-2.

The prototype chains for square and rect show that both inherit from Rectangle.prototype and Object.prototype, but only square inherits from Square.prototype.

Figure 5-2. The prototype chains for square and rect show that both inherit from Rectangle.prototype and Object.prototype, but only square inherits from Square.prototype.

Square.prototype doesn’t actually need to be overwritten with a Rectangle object, though; the Rectangle constructor isn’t doing anything that is necessary for Square. In fact, the only relevant part is that Square.prototype needs to somehow link to Rectangle.prototype in order for inheritance to happen. That means you can simplify this example by using Object.create() once again.

// inherits from Rectangle

function Square(size) {

this.length = size;

this.width = size;

}

Square.prototype = Object.create(Rectangle.prototype, {

constructor: {

configurable: true,

enumerable: true,

value: Square,

writable: true

}

});

Square.prototype.toString = function() {

return "[Square " + this.length + "x" + this.width + "]";

};

In this version of the code, Square.prototype is overwritten with a new object that inherits from Rectangle.prototype, and the Rectangle constructor is never called. That means you don’t need to worry about causing an error by calling the constructor without arguments anymore. Otherwise, this code behaves exactly the same as the previous code. The prototype chain remains intact, so all instances of Square inherit from Rectangle.prototype and the constructor is restored in the same step.

NOTE

Always make sure that you overwrite the prototype before adding properties to it, or you will lose the added methods when the overwrite happens.

Constructor Stealing

Because inheritance is accomplished through prototype chains in JavaScript, you don’t need to call an object’s supertype constructor. If you do want to call the supertype constructor from the subtype constructor, then you need to take advantage of how JavaScript functions work.

In Chapter 2, you learned about the call() and apply() methods, which allow functions to be called with a different this value. That’s exactly how constructor stealing works. You simply call the supertype constructor from the subtype constructor using either call() or apply() to pass in the newly created object. In effect, you’re stealing the supertype constructor for your own object, as in this example:

function Rectangle(length, width) {

this.length = length;

this.width = width;

}

Rectangle.prototype.getArea = function() {

return this.length * this.width;

};

Rectangle.prototype.toString = function() {

return "[Rectangle " + this.length + "x" + this.width + "]";

};

// inherits from Rectangle

function Square(size) {

Rectangle.call(this, size, size);

// optional: add new properties or override existing ones here

}

Square.prototype = Object.create(Rectangle.prototype, {

constructor: {

configurable: true,

enumerable: true,

value: Square,

writable: true

}

});

Square.prototype.toString = function() {

return "[Square " + this.length + "x" + this.width + "]";

};

var square = new Square(6);

console.log(square.length); // 6

console.log(square.width); // 6

console.log(square.getArea()); // 36

The ❶ Square constructor calls the Rectangle constructor and passes in this as well as size two times (once for length and once for width). Doing so creates the length and width properties on the new object and makes each equal to size. This is the way to avoid redefining properties from a constructor from which you want to inherit. You can add new properties or override existing ones after applying the super type constructor.

This two-step process is useful when you need to accomplish inheritance between custom types. You’ll always need to modify a constructor’s prototype, and you may also need to call the supertype constructor from within the subtype constructor. Generally, you’ll modify the prototype for method inheritance and use constructor stealing for properties. This approach is typically referred to as pseudoclassical inheritance because it mimics classical inheritance from class-based languages.

Accessing Supertype Methods

In the previous example, the Square type has its own toString() method that shadows toString() on the prototype. It is fairly common to override supertype methods with new functionality in the subtype, but what if you still want to access the supertype method? In other languages, you might be able to say super.toString(), but JavaScript doesn’t have anything similar. Instead, you can directly access the method on the supertype’s prototype and use either call() or apply() to execute the method on the subtype object. For example:

function Rectangle(length, width) {

this.length = length;

this.width = width;

}

Rectangle.prototype.getArea = function() {

return this.length * this.width;

};

Rectangle.prototype.toString = function() {

return "[Rectangle " + this.length + "x" + this.height + "]";

};

// inherits from Rectangle

function Square(size) {

Rectangle.call(this, size, size);

}

Square.prototype = Object.create(Rectangle.prototype, {

constructor: {

configurable: true,

enumerable: true,

value: Square,

writable: true

}

});

// call the supertype method

❶ Square.prototype.toString = function() {

var text = Rectangle.prototype.toString.call(this);

return text.replace("Rectangle", "Square");

};

In this version of the code, ❶ Square.prototype.toString() calls Rectangle.prototype.toString() by using call(). The method just needs to replace "Rectangle" with "Square" before returning the resulting text. This approach may seem a bit verbose for such a simple operation, but it is the only way to access a supertype’s method.

Summary

JavaScript supports inheritance through prototype chaining. A prototype chain is created between objects when the [[Prototype]] of one object is set equal to another. All generic objects automatically inherit from Object.prototype. If you want to create an object that inherits from something else, you can use Object.create() to specify the value of [[Prototype]] for a new object.

You accomplish inheritance between custom types by creating a prototype chain on the constructor. By setting the constructor’s prototype property to another value, you create inheritance between instances of the custom type and the prototype of that other value. All instances of that constructor share the same prototype, so they all inherit from the same object. This technique works very well for inheriting methods from other objects, but you cannot inherit own properties using prototypes.

To inherit own properties correctly, you can use constructor stealing, which is simply calling a constructor function using call() or apply() so that any initialization is done on the subtype object. Combining constructor stealing and prototype chaining is the most common way to achieve inheritance between custom types in JavaScript. This combination is frequently called pseudoclassical inheritance because of its similarity to inheritance in class-based languages.

You can access methods on a supertype by directly accessing the supertype’s prototype. In doing so, you must use call() or apply() to execute the supertype method on the subtype object.


[1] See Douglas Crockford’s “Code Conventions for the JavaScript Programming Language” (http://javascript.crockford.com/code.html).