Skip to content

Instantly share code, notes, and snippets.

@caseman
Created August 22, 2012 19:49
Show Gist options
  • Save caseman/3428752 to your computer and use it in GitHub Desktop.
Save caseman/3428752 to your computer and use it in GitHub Desktop.
Ctor: Lightweight Javascript Constructors with Inheritance

Ctor: Lightweight Javascript Constructors with Inheritance

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'
@joproulx
Copy link

joproulx commented Oct 4, 2012

Hello,
I am testing your code to use in my project. One thing that I have noted is that private members do not work. They act as if they are static members. Something like this:

var Foo = Ctor(function() {
  var _value;
  this.init=function(value){
    _value = value;
  }
  this.toString = function() {
    return _value;
  }
});
var foo1 = new Foo("Foo1");
var foo2 = new Foo("Foo2");
log(foo1); //Foo2  ** Error ** should be Foo1!
log(foo2); //Foo2

Any idea on how this could be fixed without breaking too much your pattern?

@al6x
Copy link

al6x commented Sep 1, 2023

Awesome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment