Inheritance in JavaScript is a little difficult to understand at first because:
- JavaScript is a prototypal object-oriented programming language (i.e. objects directly inherit from other objects). This means that there's no distinction between classes and objects. Objects which are used as classes are called prototypes.
- Unfortunately, the traditional way to create an instance of a prototype is by using
new
(which makes people think that the instance inherits from the constructor function, and not the prototype). This is called the constructor pattern, and it's the main reason of confusion in JavaScript.
For this reason Object.create
was introduced. It allowed objects to directly inherit from other objects. However, Object.create
is slow as compared to using new
. I had the same problem that you did and I was looking for an alternative; and I did come up with one.
The Traditional Way of OOP in JavaScript
Consider the following code:
function Person(firstname, lastname, gender) {
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
}
Person.prototype.getFullname = function () {
return this.firstname + " " + this.lastname;
};
Man.prototype = new Person;
Man.prototype.constructor = Man;
function Man(firstname, lastname) {
Person.call(this, firstname, lastname, "M");
}
var bobMarley = new Man("Bob", "Marley");
alert(bobMarley.getFullname());
This way of writing code suffers from several problems:
- There is no encapsulation. The constructor function and the prototype methods are defined all over the place. It look incoherent. Like shaghetti. It doesn't look like one logical unit.
- We make
Man.prototype
inherit from Person.prototype
by setting it to new Person
. However, in doing so we're initializing the firstname
, lastname
and gender
properties on Man.prototype
which is wrong.
The New Way of OOP in JavaScript
With the introduction of Object.create
we can now write code like this:
function Person(firstname, lastname, gender) {
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
}
Person.prototype.getFullname = function () {
return this.firstname + " " + this.lastname;
};
Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;
function Man(firstname, lastname) {
Person.call(this, firstname, lastname, "M");
}
var bobMarley = new Man("Bob", "Marley");
alert(bobMarley.getFullname());
The only change is that instead of Man.prototype = new Person
we write Man.prototype = Object.create(Person.prototype)
. This solves the second problem of the traditional method. However, the code still looks like spaghetti.
However, Object.create
is quite powerful. You could also use it to write object-oriented code without creating constructor functions at all. Some people call this the initializer pattern:
var person = {
init: function (firstname, lastname, gender) {
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
},
getFullname: function () {
return this.firstname + " " + this.lastname;
}
};
var man = Object.create(person, {
init: {
value: function (firstname, lastname) {
person.init.call(this, firstname, lastname, "M");
}
}
});
var bobMarley = Object.create(man);
bobMarley.init("Bob", "Marley");
alert(bobMarley.getFullname());
This solves all the problems of the traditional method. However, it also introduces some new problems of its own:
- The way of creating instances of prototypes is not consistent with the way of creating object literals.
- You have to create an instance using
Object.create
and then initialize the new object using init
. This is much slower than simply using new
.
My Way of OOP is JavaScript
To solve this problem, I wrote my own functions for OOP in JavaScript:
var Person = defclass({
constructor: function (firstname, lastname, gender) {
this.firstname = firstname;
this.lastname = lastname;
this.gender = gender;
},
getFullname: function () {
return this.firstname + " " + this.lastname;
}
});
var Man = extend(Person, {
constructor: function (firstname, lastname) {
Person.call(this, firstname, lastname, "M");
}
});
var bobMarley = new Man("Bob", "Marley");
alert(bobMarley.getFullname());
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
function extend(constructor, properties) {
var prototype = Object.create(constructor.prototype);
var keys = Object.keys(properties);
var length = keys.length;
var index = 0;
while (index < length) {
var key = keys[index++];
prototype[key] = properties[key];
}
return defclass(prototype);
}
I defined two functions defclass
and extend
for OOP in JavaScript. The defclass
function creates a “class” from a prototype. This is possible because prototypes and classes are isomorphic.
The extend function is for inheritance. It creates an instance of the prototype
of a constructor
and copies some properties onto it before returning the “class” of the new prototype.
This is the way I currently create prototype chains in JavaScript. It has the following advantages over other methods:
- Every “class” is encapsulated. There are no prototype methods dangling all over the place. It doesn't look like spaghetti.
- The
extend
function uses Object.create
for inheritance. Hence no extra properties are added to the new prototype. It's a blank prototype.
- You don't have to worry about resetting the
constructor
property on the prototype
. It is automatically done for you.
- The
defclass
and the extend
functions are consistent unlike object literals and the Object.create
functions in the initializer pattern.
- We create instances using
new
instead of Object.create
and init
. Hence the resulting code is much faster.
I could be wrong now, but I don't think so. Hope that helps.