Skip to content

Instantly share code, notes, and snippets.

@samleb
Forked from tobie/gist:43064
Created May 28, 2009 06:55
Show Gist options
  • Save samleb/119147 to your computer and use it in GitHub Desktop.
Save samleb/119147 to your computer and use it in GitHub Desktop.
/*
Simpler, more robust super keyword for Prototype.
Given the following parent class:
var Person = Class.create({
initialize: function(name) {
this.name = name;
},
say: function(message) {
return this.name + ': ' + message;
}
});
Subclassing with Class#callSuper:
var Pirate = Class.create(Person, {
say: function(message) {
return this.callSuper('say', message) + ', yarr!';
}
});
... and using Class#applySuper, you can directly pass the arguments object:
var Pirate = Class.create(Person, {
say: function() {
return this.applySuper('say', arguments) + ', yarr!';
}
});
Of course this also allows calling other methods of the subclass (Whether this is a good or a bad thing is a whole other topic).
var Pirate = Class.create(Person, {
say: function() {
return this.applySuper('say', arguments) + ', yarr!';
},
yell: function(message) {
return this.callSuper('say', message.toUpperCase());
}
});
*/
(function() {
var slice = Array.prototype.slice;
function callSuper(methodName) {
return this.applySuper(methodName, slice.call(arguments, 1));
}
/**
* Here we're fixing the infinite loop issue (super method calling itself a super method)
**/
function applySuper(methodName, args) {
// Keep a reference to the current ancestor we're resolving methods into
var ref = this._ancestor;
var ancestor = ref || this;
// Walk the prototype chain to find the first ancestor with a different implementation for this method
while (ancestor[methodName] === ref[methodName]) {
ancestor = ancestor.constructor.superclass.prototype;
}
// Store it so the whole thing is recursive
this._ancestor = ancestor;
try {
// Invoke the super method
return ancestor[methodName].apply(this, args);
} finally {
// Don't forget to reset to the original ancestor
this._ancestor = ref;
}
}
return {
callSuper: callSuper,
applySuper: applySuper
}
})();
/**
* It still remains a problem though : method resolution isn't really dynamic
* i.e. :
**/
var A = Class.create({
foo: function() {
log("foo in A");
this.bar();
},
bar: function() {
log("bar in A");
}
});
var B = Class.create(A, {
foo: function() {
log("foo in B")
this.callSuper('foo');
},
bar: function() {
log("bar in B");
this.callSuper('bar');
}
});
new B().foo();
/**
* Expected:
* foo in B
* foo in A
* bar in B
* bar in A
*
* Actual:
* foo in B
* foo in A
* bar in B
* boom -> tries to resolve `bar` in `A.superclass` which doesn't exist
**/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment