Skip to content

Instantly share code, notes, and snippets.

@JanMiksovsky
Created October 5, 2011 18:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JanMiksovsky/1265237 to your computer and use it in GitHub Desktop.
Save JanMiksovsky/1265237 to your computer and use it in GitHub Desktop.
_super() implementation for JavaScript prototype-based class systems (e.g., jQuery) allows a function to call a function of the same name in a superclass
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="http://code.jquery.com/qunit/git/qunit.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
<script>
/*
* Call a function of the same name in a superclass.
*
* E.g., if A is a superclass of B, then:
*
* A.prototype.calc = function ( x ) {
* return x * 2;
* }
* B.prototype.calc = function ( x ) {
* return this._super( x ) + 1;
* }
*
* var b = new B();
* b.calc( 3 ); // = 7
*
* This assumes a standard prototype-based class system in which all classes have
* a member called "superclass" pointing to their parent class, and all instances
* have a member called "constructor" pointing to the class which created them.
*
* This routine has to do some work to figure out which class defined the
* calling function. It will have to walk up the class hierarchy and,
* if we're running in IE, do a bunch of groveling through function
* definitions. To speed things up, the first call to _super() within a
* function creates a property called "_superFn" on the calling function;
* subsequent calls to _super() will use the memoized answer.
*
* Some prototype-based class systems provide a _super() function through the
* use of closures. The closure approach generally creates overhead whether or
* not _super() will ever be called. The approach below adds no overhead if
* _super() is never invoked, and adds minimal overhead if it is invoked.
* This code relies upon the JavaScript .caller method, which many claims
* has slow performance because it cannot be optimized. However, "slow" is
* a relative term, and this approach might easily have acceptable performance
* for many applications.
*/
function _super() {
// Figure out which function called us.
var callerFn = ( _super && _super.caller )
? _super.caller // Modern browser
: arguments.callee.caller; // IE9 and earlier
if ( !callerFn ) {
return undefined;
}
// Have we called super() within the calling function before?
var superFn = callerFn._superFn;
if ( !superFn ) {
// Find the class implementing this method.
var classInfo = findMethodImplementation( callerFn, this.constructor );
if ( classInfo ) {
var classFn = classInfo.classFn;
var callerFnName = classInfo.fnName;
// Go up one level in the class hierarchy to get the superfunction.
superFn = classFn.superclass.prototype[ callerFnName ];
// Memoize our answer, storing the value on the calling function,
// to speed things up next time.
callerFn._superFn = superFn;
}
}
return superFn
? superFn.apply( this, arguments ) // Invoke superfunction
: undefined;
};
/*
* Find which class implements the given method, starting at the given
* point in the class hierarchy and walking up.
*
* This is done by enumerating all class prototype members to find the
* function identical to the method we're looking for.
*
* Returns the class that implements the function, and the name of the class
* member that references it. Returns null if the class was not found.
*/
function findMethodImplementation( methodFn, classFn ) {
// See if this particular class defines the function.
var prototype = classFn.prototype;
for ( var key in prototype ) {
if ( prototype[ key ] === methodFn ) {
// Found the function implementation.
// Check to see whether it's really defined by this class,
// or is actually inherited.
var methodInherited = classFn.superclass
? prototype[ key ] === classFn.superclass.prototype[ key ]
: false;
if ( !methodInherited ) {
// This particular class defines the function.
return {
classFn: classFn,
fnName: key
};
}
}
}
// Didn't find the function.
if ( classFn.superclass ) {
// Look in parent classes.
return findMethodImplementation( methodFn, classFn.superclass );
} else {
return null;
}
}
/*
* Sample use, building on the standard prototype-based classes in jQuery.
* This is simply for demonstration purposes; there's no hard dependency
* between this _super() implementation and jQuery; the method could easily
* be adapted to any standard prototype-based class system.
*/
jQuery.fn._super = _super;
/*
* Three sample subclasses: jQuery <- A <- B <- C
*/
var A = jQuery.sub();
var B = A.sub();
var C = B.sub();
A.prototype.extend({
decorate: function ( s ) {
return "(a: " + s + ")";
},
calc: function ( x ) {
return x * 2;
}
});
B.prototype.extend({
decorate: function ( s ) {
return "(b: " + this._super( s ) + ")";
},
calc: function ( x ) {
return this._super( x ) + 1;
}
});
C.prototype.extend({
decorate: function ( s ) {
return "(c: " + this._super( s ) + ")";
}
/* Omit a definition of "calc" */
});
/*
* QUnit unit tests
*/
test( "_super", function() {
var c = C();
equal( c.decorate( "Hello" ), "(c: (b: (a: Hello)))" );
equal( c.calc(3), 7 );
var b = B();
equal( b.decorate( "Hello" ), "(b: (a: Hello))" );
equal( b.calc(3), 7 );
var a = A();
equal( a.decorate( "Hello" ), "(a: Hello)" );
equal( a.calc(3), 6 );
});
</script>
<body>
<h1 id="qunit-header">_super tests</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup, will be hidden</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment