Author: Casey Duncan @iamnotcasey
This Javascript constructor with inheritance pattern is designed as a lightweight alternative to other methods I've seen while still providing a nice abstraction as a 13 line function that can be easily inlined into your code.
A big difference using Ctor
vs. other popular inheritance solutions is
its use of a "member init" function, instead of an object literal to define the members
of the prototype. I personally find using object literals awkward and
unwieldy for defining member methods. The function also gives you
a closure which makes private definitions, and caching values in the
constructor convenient and easy. Ctor
passes the "member init" function the
parent prototype, so it can easily call methods on it when extending a parent
method. This provides super
-like functionality simply and without undue magic.
One important thing about Ctor
is that it is not doing anything other
than straightforward prototype inheritance. There is no universal base "class",
and in fact it isn't an attempt at class-based inheritance at all. The
resulting constructors are normal Javascript functions. It also avoids
using any ES5-specific functionality like Object.create()
.
This implementation of Ctor
requires the use of new
with the resulting
constructors, but this could be avoided with a few more lines of code. I know
some folks hate new
with a passion, I myself find it can aid readability,
but of course you can decide for yourself.
This code is released under the MIT license. Feel free to use it as you see fit. If you see ways to improve it, leave a comment. Enjoy.
function Ctor(Parent, memberInit) {
if (!memberInit) {
memberInit = Parent;
Parent = function(){};
}
var F = function(c) {
if (this.init && c !== Ctor) {
this.init.apply(this, arguments);
}
}
memberInit.call(F.prototype = new Parent(Ctor), Parent.prototype);
return F;
}
//// Examples ////
// With only one arg, Ctor creates a base constructor
// The "member init" function is called with `this` as the constructor
// prototype for convenient manipulation.
var Base = Ctor(function() {
this.toString = function() {
return this.first + ' ' + this.last;
}
});
var Duncan = Ctor(Base, function() {
this.last = 'Duncan';
});
var Casey = Ctor(Duncan, function() {
this.first = 'Casey';
});
// If you provide an `init` member function, it will be called
// at construction passing the arguments given to the constructor.
// `this` is bound to the new instance inside `init()` as you would expect.
var Anyone = Ctor(Duncan, function() {
this.init = function(first, last) {
this.first = first || this.first;
this.last = last || this.last;
}
});
// The "member init" function to Ctor is passed the parent constructor's
// prototype for convenience. This allows you to very easily invoke
// methods of the parent when inheriting.
var Middle = Ctor(Anyone, function(sup) {
this.init = function(first, middle, last) {
sup.init.call(this, first, last);
this.middle = middle;
}
this.toString = function() {
return this.first + ' ' + this.middle + ' ' + this.last;
}
});
function log(what) {
console.log(String(what));
}
log(new Casey); // => 'Casey Duncan'
log(new Anyone('Lynelle')); // => 'Lynelle Duncan'
log(new Anyone('Robert', 'Fripp')); // => 'Robert Fripp'
log(new Middle('Ronnie', 'James')); // => 'Ronnie James Duncan'
log(new Middle('Ronnie', 'James', 'Dio')); // => 'Ronnie James Dio'
Awesome!