The caller extension pattern is a design pattern I’ve discovered, which let’s you create constructors that can inherit from any other constructor. To do this we get to use some javascript magic: the call and apply methods. So let’s see some code!
function MySuper() {
var counter = 0;
this.count = function () {
return counter++;
}
}
function MySub() {
// Add all properties in MySuper to MySub
MySuper.call(this);
// Construct MySub
}
foo = new MySub();
foo.count() // 1
bar = new MySub();
bar.count() // 1
In this example MySuper gets called using the call method inside MySub, passing the new object which MySub is constructing as a parameter. MySuper will then assign properties to the new object, giving them their own scope to work with. Because MySuper gets called every time MySub is called, the object constructed by MySub will not share any properties with other objects constructed with MySub. Thus avoiding the problems we’ve seen with prototype inheritance.
How call and apply work
Call and apply are properties of the native Function object from which all function inherit. They allows you to tell the function what object should be referred to by the ‘this’ variable as it is called. In order to understand how call and apply work it’s important to understand how ‘this’ referencing works.
When a function is called using it as a method (ie. myObject.myFunc()), ‘this’ will refer to the object from which it is called. When called as a stand-alone function ‘this’ will refer to the global object. And when a function is called using the new keyword ‘this’ will refer to the new object. That’s why if you assign properties to ‘this’ inside a constructor, they will be properties of the object you’re constructing. The following examples demonstrate this principle:
function count() {
if (!this.counter) {
this.counter = 0;
}
return this.counter++;
}
// Stand-alone call
var counter = 10;
count(); // 11, 'this.counter' refers to the global variable 'counter'.
// Called as a method
myObject = {
counter: 20,
count : myFunc
}
myObject.count() // 21, this.counter refers to the property on myObject
// Called as a constructor
c = new count();
c.counter // 1, this.counter doesn't yet exist as 'this' is a new object', so 0 is assigned to it.
With call and apply you can assign an object to be ‘this’ in the function. The difference between call and apply is that apply takes an array of parameters to pass to the function, and call passes every parameter except the first to the function. So
count.call(myObject, 1, 2, 3); // is the same as: count.apply(myObject, [1, 2, 3]);
Now if we look back at the Caller extension pattern, you can see that the object that is constructed for MySub, gets properties assigned to it by MySuper when called using MySuper.call(this).
Super-sub relationship
Unlike prototype inheritance, and unlike classical inheritance the caller extension pattern doesn’t create a way to distinguish between the properties added by a parent constructor, or by it’s own constructor. When testing with the hasOwnProperty() method, both methods from the super and sub constructor are considered to be the object’s own property.
The reason you might want to distinguish between these two is that in some cases it may be necessary to override a method from the super class, but still call this super method from within the new sub method. There is quite an easy way around this problem:
function MySub() {
MySuper.call(this);
// store the parent method in a private variable
var superFunc = this.myFunc;
// Override the parent method
this.myFunc = function () {
superFunc.call(this);
// do some more stuff
}
}
Or you could write it using a modified module pattern – a kind of property assinging module:
function MySub() {
MySuper.call(this);
(function (superFunc) {
this.myFunc = function() {
superFunc.call(this);
// do more stuff
}
}.call(this, this.myFunc))
}
Even though the object constructed using the Caller extension pattern isn’t technically inheriting from it’s parent, the effect is exactly the same.
Prototype and caller extension
Objects created with the sub constructor will not inherit from the prototype of the super constructor. This does mean that using this design pattern excludes you from also using prototype as a way to extend constructors. There are many reasons why you shouldn’t be using prototype to extend constructors at all. So in my own opinion this is more a gain then a loss. For one, adding code to a constructor that’s not actually inside the constructor can lead to messy and poorly maintainable code. Things like these just shouldn’t be done:
// Don't do this:
function MySuper() {
this.foo = 1;
}
function MySub() {
MySuper.call(this);
}
MySuper.prototype.bar = 2;
var foo = new MySub();
foo.bar // undefined, MySuper.prototype doesn't apply to objects constructed with MySub
To conclude
The Caller extension pattern is an effective way to use inheritance of constructor functions as you would inheritance of classes in a classical OOP language. Using return will even allow you to pass protected variables from a super class to a sub class (more on this in a later date). Not only does Caller extension create reliable object extension, it also helps to keep similar functionality together. Because call is used inside the constructor, everything that constructs an object is contained inside the constructor. Making code easier to maintain.