Created
June 25, 2012 16:25
-
-
Save joelpt/2989570 to your computer and use it in GitHub Desktop.
Javascript fancy inheritance extendClass()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////////////////////////////////////////////////////// | |
// 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