Skip to content

Instantly share code, notes, and snippets.

@jashkenas
Created November 1, 2011 17:45
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jashkenas/1331310 to your computer and use it in GitHub Desktop.
Save jashkenas/1331310 to your computer and use it in GitHub Desktop.
// Demonstration of dynamic super() calls.
// Because of JS reserved words, "ssuper()" is the method name,
// and is passed the current object, as well as the name of
// the current method.
function GreatGrandParent() {};
GreatGrandParent.prototype.method = function() {
console.log("In the GreatGrandParent.");
};
function GrandParent() {};
GrandParent.prototype = new GreatGrandParent;
GrandParent.prototype.method = function() {
ssuper(this, 'method');
console.log("In the GrandParent.");
};
function Parent() {};
Parent.prototype = new GrandParent;
Parent.prototype.method = function() {
ssuper(this, 'method');
console.log("In the Parent.");
};
function Child() {};
Child.prototype = new Parent;
Child.prototype.method = function() {
ssuper(this, 'method');
console.log("In the Child.");
};
function ssuper(object, method) {
// Initialize an object-specific super depth counter. If desired, the counter
// can be specific to per-object-per-method-name.
var depth = object._superCount || (object._superCount = 1);
var proto = object.__proto__;
// Walk the prototype chain to the correct level of "super" ness.
while(depth--) proto = proto.__proto__;
// Increment the super counter.
object._superCount++;
// Actually call super().
proto[method].call(object);
// Decrement the super counter.
object._superCount--;
// We're done with this particular recursive super() call. Remove the record.
if (object._superCount <= 1) delete object._superCount;
};
(new Child).method();
// Pasting the above block of code into a browser console yields:
//
// In the GreatGrandParent.
// In the GrandParent.
// In the Parent.
// In the Child.
//
@DmitrySoshnikov
Copy link

This one avoids while-loop for lookup: https://gist.github.com/1330574

Dmitry.

@arv
Copy link

arv commented Nov 1, 2011

What happens when a method that has been called through super calls another method that calls super as well? You need to reset the counter at least.

var a = {
  m: function() {
    console.log('a.m');
  },
  n: function() {
    console.log('a.n');
  }
};

var b = {
  __proto__: a,
  m: function() {
    this.n();  // this needs to reset the _superCount
  }
  n: function() {
    console.log('b.n');
  }
};

var c = {
  __proto__: b,
  m: function() {
    ssuper(this, 'm');
  },
  n: function() {
    ssuper(this, 'n');
  }
};

a.n();

@jashkenas
Copy link
Author

@arv: Yes, it does. Ideally, you'd push a new super counter record onto the stack, and pop it back off when this.n() exits.

@rauschma
Copy link

rauschma commented Nov 1, 2011

What if the method making the first super-call isn’t at depth 1? It could be at a much higher depth.

@rauschma
Copy link

rauschma commented Nov 1, 2011

Similar problem: In the prototype chain, there might be objects that don’t have the method, those must be skipped when looking for the super-method.

@jashkenas
Copy link
Author

@rauschma: Nope, and nope.

The correct behavior is for an external call to start out at the bottom of the inheritance chain, and work upwards through super() calls. Any call that just jumps to a parent implementation of a method that is overridden further below seriously breaks encapsulation. If you extend a class, and override a method with enhanced behavior, you expect your implementation to be called, not skipped arbitrarily.

If there are objects in the prototype chain that don't have the method ... it doesn't make any difference, because the method name resolves to nearest parent that does have the method. That's how a lookup of any property along the prototype chain works.

@rauschma
Copy link

rauschma commented Nov 3, 2011

@jashkenas: Example.

function A() {
}
A.prototype.desc = function() {
    console.log("A");
}

function B() {
}
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
B.prototype.desc = function() {
    console.log("B");
    ssuper(this, "desc");
}

function C() {
}
C.prototype = Object.create(B.prototype);
C.prototype.constructor = C;

new C().desc()

Output:

B
B
A

When B.prototype.desc() makes the super-call, it initially calls itself.

I contend that you need to somehow record in _superCount where you actually found a given property. For example: gist.github.com/1331748

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