One more way of implementing inheritance (the last one in the chapter, I promise) has to do again with constructor functions and not the objects directly. In this pattern, the constructor of the child calls the constructor of the parent using either the call()
or apply()
method. This can be called stealing a constructor or inheritance by borrowing a constructor if you want to be more subtle about it.
The call()
and apply()
methods were discussed in Chapter 4, Objects, but here's a refresher; they allow you to call a function and pass an object that the function should bind to its this
value. So for inheritance purposes, the child constructor calls the parent's constructor and binds the child's newly created this
object as the parent's this
.
Let's have this parent constructor Shape()
:
function Shape(id) { this.id = id; } Shape.prototype.name = 'Shape'; Shape.prototype.toString = function () { return this.name; };
Now, let's define Triangle()
, which uses apply()
to call the Shape()
constructor, passing this
(an instance created with new Triangle()
) and any additional arguments:
function Triangle() { Shape.apply(this, arguments); } Triangle.prototype.name = 'Triangle';
Note that both Triangle()
and Shape()
have added some extra properties to their prototypes.
Now, let's test this by creating a new triangle
object:
>var t = new Triangle(101); >t.name; "Triangle"
The new triangle
object inherits the id
property from the parent, but it doesn't inherit anything added to the parent's prototype
:
>t.id; 101 >t.toString(); "[object Object]"
The triangle failed to get the Shape
function's prototype properties because there was never a new Shape()
instance created, so the prototype was never used. However, you saw how to do this at the beginning of this chapter. You can redefine Triangle
as follows:
function Triangle() { Shape.apply(this, arguments); } Triangle.prototype = new Shape(); Triangle.prototype.name = 'Triangle';
In this inheritance pattern, the parent's own properties are recreated as the child's own properties. If a child inherits an array or other object, it's a completely new value (not a reference), and modifying it won't affect the parent.
The drawback is that the parent's constructor gets called twice-once with apply()
to inherit own properties and once with new
to inherit the prototype. In fact, the own properties of the parent are inherited twice. Let's take this simplified scenario:
function Shape(id) { this.id = id; } function Triangle() { Shape.apply(this, arguments); } Triangle.prototype = new Shape(101);
Here, we will create a new instance:
>var t = new Triangle(202); >t.id; 202
There's an own property id,
but there's also one that comes down the prototype chain, ready to shine through:
>t.__proto__.id; 101 > delete t.id; true >t.id; 101
The problem of the double work performed by calling the constructor twice can easily be corrected. You can call apply()
on the parent constructor to get all own properties and then copy the prototype's properties using a simple iteration (or extend2()
as discussed previously):
function Shape(id) { this.id = id; } Shape.prototype.name = 'Shape'; Shape.prototype.toString = function () { return this.name; }; function Triangle() { Shape.apply(this, arguments); } extend2(Triangle, Shape); Triangle.prototype.name = 'Triangle';
Lets test the following code:
>var t = new Triangle(101); >t.toString(); "Triangle" >t.id; 101
No double inheritance:
>typeoft.__proto__.id; "undefined"
The extend2()
method also gives access to uber
if needed:
>t.uber.name; "Shape"
3.141.30.162