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.