Classical inheritance
Tuesday, September 15th, 2009Let's talk a bit about classical inheritance. The word "classical" is not used in the sense of old-school middle-age has-been-around forever kind of meaning.
Classical just means that you think in terms of classes. Your objects are created by constructor functions and you want objects created from one constructor Child()
to get properties that come from another constructor Parent()
.
function Parent(){ this.name = 'Adam'; } Parent.prototype.say = function(){ return this.name; }; function Child(){}
So we call a function called inherit()
and let’s see some patterns for implementing this function.
inherit(Child, Parent);
Option #1 - The ECMA way
Here's the ECMA standard way. You create an object using the Parent()
and assign this object to the Child()
's prototype.
Than when you do new Child()
, it will get functionality from the Parent()
instance via the prototype.
function inherit(C, P) { C.prototype = new P(); }
This pattern has the drawback that it’s tough to pass arguments to the parent during the call to new Child()
Option #2 - Rent-a-constructor
This next pattern solves the problem of passing arguments. It borrows the parent constructor passing the child object to be bound to this and passing any arguments.
This way you can only inherit properties added to this
inside the parent constructor.
You don't inherits stuff that was added to the prototype.
function C(a, c, b, d) { P.call(this, arguments); }
Option #3 - rent + prototype
Combining the previous two patterns you can pass arguments by borrowing the constructor and also inherit from the prototype.
function C(a, c, b, d) { P.call(this, arguments); } C.prototype = new P();
Drawback is that the parent constructor is called twice.
Option #3
Here's another way to do classical inheritance that doesn't include calling the parent constructor at all.
The rule of thumb is that reusable stuff should go to the prototype.
So for inheritance purposes, anything interesting should be in the prototype. Can you just say that child’s is the parent’s prototype? Yes, you can.
function inherit(C, P) { C.prototype = P.prototype; }
This gives you really short prototype chain lookups because everybody has the same prototype. But that’s also a drawback because if a child down the inheritance chain modifies the prototype, it affects all parents.
Option #5
This next pattern solves the same-prototype problem by breaking the direct link and benefiting from the prototype chain.
Here you have an empty function which gets the prototype of the parent. The prototype of the child is an instance of the blank function.
function inherit(C, P) { var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); }
This pattern has a behavior slightly different from the ECMA standard suggestion because here you only inherit properties of the prototype. And that’s fine, actually preferable. Like I said – the prototype is the place for reusable functionality. Anything the parent ads to this
inside the constructor is ignored.
Option #5 + super
Building on top of the previous pattern, you can add a reference to the original parent. This is like having access to the superclass in other languages.
The property is called uber
because super
is a reserved word and superclass
may lead the non-suspecting developer down the path of thinking that JavaScript has classes. And we don’t want that, right?
function inherit(C, P) { var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; }
Option #5 + super + constructor reset
One last thing to do in this almost perfect classical inheritance function is to reset the pointer to the constructor function in case we need it down the road.
function inherit(C, P) { var F = function(){}; F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; }
If you don’t reset the constructor pointer, objects created with the Child()
constructor will report that they were created with the Parent()
constructor.
A function like this exists in the YUI library and helps you with your classical inheritance needs.