Real Strong and Easy Inheritance for JS





5.00/5 (6 votes)
The real answer was the most simple
Introduction
JavaScript is a very powerful language. It looks simple, but it becomes very messy if you don't know what you're doing. Inheritance in JavaScript is the perfect example of it.
Background
I've been looking on the internet for days for the best implementation of inheritance. Let's face it... the most common answers are very complex. There is a very simple method with modern browsers if you understand these three keywords:
__proto__
prototype
constructor
The Core Mechanism of Inheritance: __Proto__
Every object in JS has the hidden property __proto__
. When you use one property that is not defined in the object, JS automatically tries to find the property in the object in __proto__
:
var o = {};
o.toString();
In this example, it's obvious that the function toString
is not declared in the o
object. JS will look in the __proto__
of o
to find this function and eventually JS will use: o.__proto__.toString()
This is the mechanism you have to use to inherit one object to an other.
var Mammal = {
legs: 4
};
var Cat = {
fur: true
};
// Do inheritance
Cat.__proto__ = Mammal;
Cat.legs
will be translated into Cat.__proto__.legs
at runtime.
A Class Is Not an Object!
There is a real difference between objects and classes that people tend to forget in JS. Classes are not objects.
Classes describe objects like templates. Objects are instances of classes.
The Class Template: Prototype
In JS, you create classes using functions - this may be a little odd at the beginning.
var Mammal = function () { // Create the Mammal class
};
When using functions as classes, you use another hidden properties: prototype
. This is where most of the misunderstanding takes place: prototype
and __proto__
are not the same thing! prototype
describes what properties will be inherited when the object will be created (with the new mechanism).
In JS, when you do:
var m = new Mammal();
This is what JS real does:
var m = {};
m.__proto__ = Mammal.prototype;
Mammal.call(m);
Thus, every properties in Mammal.prototype
will be accessible in m
.
Let's create our Cat
class.
var Cat = function () { // Create the Cat class
};
And now, for the inheritance:
Cat.prototype.__proto__ = Mammal.prototype;
And that's it ! Now when you create a new Cat
, it's __proto__
will be Cat.prototype
and it's __proto__.__proto__
will be Mammal.prototype
so your new born kitten will have access to the Cat
and Mammal
properties.
Static Properties
It's really easy to add static
properties to your class. Instead of setting your property into the prototype, you just set it in the class itself:
Mammal.legs = 4; // all mammals have 4 legs
The problem is that Cat
cannot access the legs
property. So we have to have another inheritance:
Cat.__proto__ = Mammal;
And once again, you don't have to add anything more.
The Contructor Property
I wasn't totally correct when I explained what new
does in JS. I've forgotten one line:
var m = {};
m.__proto__ = Mammal.prototype;
m.constructor = Mammal; // the missing line
m.constructor.call(m);
That means the hidden property constructor holds the class. That is useful to reach static
properties.
m.constructor.legs // 4! Mammals always have 4 legs.
The Construction Chain
When you create a new Cat
, you have to explicitly call the parent constructor. This can be achieved with __proto__
and constructor
:
var c = new Cat();
c.__proto__; // Cat.prototype
c.__proto__.__proto__; // Mammal.prototype
c.__proto__.__proto__.constructor; // Mammal
c.__proto__.__proto__.constructor.call(c); // Call the Mammal constructor
All Together
In order to simplify the inheritance process, here's a very helpful yet simple function:
Function.prototype.subClass = function(superClass) {
this.prototype.__proto__ = superClass.prototype; // Standard inheritance
this.__proto__ = superClass; // Class inherit static properties from superClass
this.prototype.superClass = superClass; // Keep track of superClass
}
The two first lines provide inheritance for properties and static
properties.
The superClass
property is a shortcut to the super/parent class - useful to call super/parent function such as constructor.
var Mammal = function () { ... }
var Cat = function () {
this.superClass(); // class Mammal constructor
}
Cat.subClass(Mammal);
Enhanced Version: The toclass Function
Here's an enhanced version that simplifies dramatically the class creation process in JS.
Function.prototype.toClass = function (superClass, members) {
if (members == undefined) {
members = superClass;
superClass = null;
}
if (superClass) {
this.prototype.__proto__ =
superClass.constructor == Object ?
superClass : // enable inheritance from object
superClass.prototype;
this.__proto__ = superClass; // Class inherit static members from superClass
this.prototype.superClass = superClass; // Keep track of superClass
this.prototype.static = this; // Keep track of statics
}
for(var k in members) {
this.prototype[k] = members[k];
}
return this;
};
When using this function, it creates a static
property that is an exact synonym of constructor
, but it enhances the readability when reaching for static
properties.
Here's an example of how to use this function:
Animal = function (){
}
Animal.type = "Animal";
Animal.ShowType = function () {
console.log(this.type);
}
Animal.toClass({
talk: function() {
console.log(this.sound+"!");
}
});
Dog = function (){
this.superClass();
}
Dog.type = "Dog";
Dog.toClass(Animal, {
sound: "bark",
});
Cat = function (){
this.superClass();
}
Cat.toClass(Animal, {
sound: "meow",
});
a = new Animal ();
a.talk(); // "undefined!"
d = new Dog ();
d.talk(); // "bark!"
c = new Cat ();
c.talk(); // "meow!"
Animal.ShowType(); // "Animal"
Dog.ShowType(); // "Dog"
c.static.type; // "Cat"
Points of Interest
Since __proto__
is not exposed in IE, this doesn't work at all with Microsoft browser.
You'll have a similar problem with Safari < 5 and Opera < 10.50.
History
- 2012-09-29: First publication
- 2012-10-02: Added list of non working browser
- 2012-10-04: Minor changes and typo corrections