No. No. No. This won't do. You're doing inheritance in JavaScript all wrong. Your code gives me migraines.
Creating a Pseudo-Classical Inheritance Pattern in JavaScript
If you want something similar to classes in JavaScript then there are a lot of libraries out there which provide it to you. For example using augment you could restructure your code as follows:
var augment = require("augment");
var ABC = augment(Object, function () {
this.constructor = function (key, value) {
this.key = key;
this.value = value;
};
this.what = function () {
alert("what");
};
});
var XYZ = augment(ABC, function (base) {
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
I don't know about you but to me this looks a lot like classical inheritance in C++ or Java. If this solves your problem, great! If is doesn't then continue reading.
Prototype-Class Isomorphism
In a lot of ways prototypes are similar to classes. In fact prototypes and classes are so similar that we can use prototypes to model classes. First let's take a look at how prototypal inheritance really works:
The above picture was taken from the following answer. I suggest you read it carefully. The diagram shows us:
- Every constructor has a property called
prototype
which points to the prototype object of the constructor function.
- Every prototype has a property called
constructor
which points to the constructor function of the prototype object.
- We create an instance from a constructor function. However the instance actually inherits from the
prototype
, not the constructor.
This is very useful information. Traditionally we've always created a constructor function first and then we've set its prototype
properties. However this information shows us that we may create a prototype object first and then define the constructor
property on it instead.
For example, traditionally we may write:
function ABC(key, value) {
this.key = key;
this.value = value;
}
ABC.prototype.what = function() {
alert("what");
};
However using our newfound knowledge we may write the same thing as:
var ABC = CLASS({
constructor: function (key, value) {
this.key = key;
this.value = value;
},
what: function () {
alert("what");
}
});
function CLASS(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
As you can see encapsulation is easy to achieve in JavaScript. All you need to do is think sideways. Inheritance however is a different issue. You need to do a little more work to achieve inheritance.
Inheritance and Super
Take a look at how augment
achieves inheritance:
function augment(body) {
var base = typeof this === "function" ? this.prototype : this;
var prototype = Object.create(base);
body.apply(prototype, arrayFrom(arguments, 1).concat(base));
if (!ownPropertyOf(prototype, "constructor")) return prototype;
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
Notice that the last three lines are the same as that of CLASS
from the previous section:
function CLASS(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
This tells us that once we have a prototype object all we need to do is get its constructor property and return it.
The first three lines of augment are used to:
- Get the base class prototype.
- Create a derived class prototype using
Object.create
.
- Populate the derived class prototype with the specified properties.
That's all that there is to inheritance in JavaScript. If you want to create your own classical inheritance pattern then you should be thinking along the same lines.
Embracing True Prototypal Inheritance
Every JavaScript programmer worth their salt will tell you that prototypal inheritance is better than classical inheritance. Nevertheless newbies who come from a language with classical inheritance always try to implement classical inheritance on top of prototypal inheritance, and they usually fail.
They fail not because it's not possible to implement classical inheritance on top of prototypal inheritance but because to implement classical inheritance on top of prototypal inheritance you first need to understand how true prototypal inheritance works.
However once you understand true prototypal inheritance you'll never want to go back to classical inheritance. I too tried to implement classical inheritance on top of prototypal inheritance as a newbie. Now that I understand how true prototypal inheritance works however I write code like this:
function extend(self, body) {
var base = typeof self === "function" ? self.prototype : self;
var prototype = Object.create(base, {new: {value: create}});
return body.call(prototype, base), prototype;
function create() {
var self = Object.create(prototype);
return prototype.hasOwnProperty("constructor") &&
prototype.constructor.apply(self, arguments), self;
}
}
The above extend
function is very similar to augment
. However instead of returning the constructor function it returns the prototype object. This is actually a very neat trick which allows static properties to be inherited. You can create a class using extend
as follows:
var Abc = extend(Object, function () {
this.constructor = function (key, value) {
this.value = 333 + Number(value);
this.key = key;
};
this.what = function () {
alert("what");
};
});
Inheritance is just as simple:
var Xyz = extend(Abc, function (base) {
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
Remember however that extend
does not return the constructor function. It returns the prototype object. This means that you can't use the new
keyword to create an instance of the class. Instead you need to use new
as a method, as follows:
var x = Xyz.new("x", "123");
var y = Xyz.new("y", "456");
var it = Abc.new("it", "789");
This is actually a good thing. The new
keyword is considered harmful and I strongly recommend you to stop using it. For example it's not possible to use apply
with the new
keyword. However it is possible to use apply
with the new
method as follows:
var it = Abc.new.apply(null, ["it", "789"]);
Since Abc
and Xyz
are not constructor functions we can't use instanceof
to test whether an object is an instance of Abc
or Xyz
. However that's not a problem because JavaScript has a method called isPrototypeOf
which tests whether an object is a prototype of another object:
alert(x.key + ": " + x.value + "; isAbc: " + Abc.isPrototypeOf(x));
alert(y.key + ": " + y.value + "; isAbc: " + Abc.isPrototypeOf(y));
alert(it.key + ": " + it.value + "; isAbc: " + Abc.isPrototypeOf(it));
alert(it.key + ": " + it.value + "; isXyz: " + Xyz.isPrototypeOf(it));
In fact isPrototypeOf
is more powerful than instanceof
because it allows us to test whether one class extends another class:
alert(Abc.isPrototypeOf(Xyz)); // true
Besides this minor change everything else works just like it did before:
x.what();
y.that();
it.what();
it.that(); // will throw; it is not Xyz and does not have that method
See the demo for yourself: http://jsfiddle.net/Jee96/
What else does true prototypal inheritance offer? One of the biggest advantages of true prototypal inheritance is that there's no distinction between normal properties and static properties allowing you to write code like this:
var Xyz = extend(Abc, function (base) {
this.empty = this.new();
this.constructor = function (key, value) {
base.constructor.call(this, key, value);
};
this.that = function () {
alert("that");
};
});
Notice that we can create instances of the class from within the class itself by calling this.new
. If this.constructor
is not yet defined then it returns a new uninitialized instance. Otherwise it returns a new initialized instance.
In addition because Xyz
is the prototype object we can access Xyz.empty
directly (i.e. empty
is a static property of Xyz
). This also means that static properties are automatically inherited and are no different from normal properties.
Finally, because the class is accessible from within the class definition as this
, you can created nested classes which inherit from the class which they are nested within by using extend
as