Skip to content

Instantly share code, notes, and snippets.

@joelpt
Created June 25, 2012 16:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joelpt/2989570 to your computer and use it in GitHub Desktop.
Save joelpt/2989570 to your computer and use it in GitHub Desktop.
Javascript fancy inheritance extendClass()
///////////////////////////////////////////////////////
// extendClass(): subclassing for Javascript
///////////////////////////////////////////////////////
// extendClass won't create surrogate child functions for these function names.
var EXTEND_CLASS_BANNED_SURROGATE_NAMES =
['constructor', '$base', '$super', '$parent'];
// Inherit superClass's prototype onto subClass.
// Adds properties of prototype argument to subClass's prototype.
//
// Adds the following additional prototype properties to subClass:
//
// $super: function to call parent functions, e.g.
// this.$super('parentFunctionName')(arg1, arg2, ...);
// This will use the correct prototype functions of the parent within
// the called super-function, so we don't have a parent trying to
// call the child's functions of the same name.
// $base: function to call parent's constructor, e.g.
// this.$base(constructorArg1, ...);
// $parent: equals the superClass object.
//
// For functions that exist on the superClass which are not explicitly
// overriden in the subClass, a surrogate function is generated of the
// same name and stored in the subClass's prototype which calls $super()
// for the given function. This ensures that for non-overriden functions,
// the parent function always gets executed with the proper parent-prototype
// context, as described above w.r.t. $super.
function extendClass(subClass, superClass, prototype) {
if (!superClass) {
superClass = Object;
}
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
for (var x in prototype) {
if (prototype.hasOwnProperty(x)) {
subClass.prototype[x] = prototype[x];
}
}
for (var x in superClass.prototype) {
if (EXTEND_CLASS_BANNED_SURROGATE_NAMES.indexOf(x) >= 0) {
// skip banned surrogate function names
continue;
}
if (!subClass.prototype.hasOwnProperty(x)) {
// subClass didn't override this superClass function,
// so create a surrogate function for it
subClass.prototype[x] = getExtendClassSurrogateFunction(x);
}
}
subClass.prototype.$super = function (propName) {
var prop = superClass.prototype[propName];
if (typeof prop !== "function") {
return prop;
}
var self = this;
return function (/*arg1, arg2, ...*/) {
var selfProto = self.__proto__;
self.__proto__ = superClass.prototype;
try {
return prop.apply(self, arguments);
}
finally {
self.__proto__ = selfProto;
}
};
};
subClass.prototype.$parent = superClass;
subClass.prototype.$base = function() {
this.$super('constructor').apply(this, arguments);
}
}
// Factory method to get a surrogate function for a child object
// to call $super on its parent object. Used when a parent object
// has a certain prototype function but child has not overriden it;
// by setting up surrogate functions on the child's prototype for these
// non-overriden functions we ensure the parent functions always get
// called with the parent's prototype context.
function getExtendClassSurrogateFunction(functionName) {
return function() {
return this.$super(functionName).apply(this, arguments);
};
}
///////////////////////////////////////////////////////
// Usage example
///////////////////////////////////////////////////////
// parent class
var Person = function(first, last) {
this.firstname = first;
this.lastname = last;
};
Person.prototype = {
getName: function() {
return this.firstname + ' ' + this.lastname;
},
getFormalName: function() {
return this.lastname + ', ' + this.firstname;
}
}
extendClass(Person, Object, Person.prototype);
// child class
var ProfessionalPerson = function(first, last, title) {
this.$base(first, last); // call parent constructor
this.title = title;
};
ProfessionalPerson.prototype = {
getTitle: function() {
return this.title;
},
// overrides parent function
getName: function() {
// call parent method; it is executed with parent's prototype context
var name = this.$super('getName')();
return this.title + ' ' + name;
}
// parent's getFormalName() is not overridden in the child class, so
// it will be given a surrogate function in the child class which ensures
// it gets executed with the parent's prototype context
}
extendClass(ProfessionalPerson, Person, ProfessionalPerson.prototype);
// usage example
var guy = new Person('John', 'Doe');
console.log(guy.getName()); // -> "John Doe"
var doctor = new ProfessionalPerson('Sigmund', 'Freud', 'Dr.');
console.log(doctor.getTitle()); // -> "Dr."
console.log(doctor.getName()); // -> "Dr. Sigmund Freud"
console.log(doctor.getFormalName()); // -> "Freud, Sigmund"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment