Preventing infinite recursion using prototype backbone-style inheritance

I am using an extension function adapted from Backbone (identical, except for a few changes to match my employer naming conventions) to implement prototype inheritance. After setting up the following structure (much simplified below) I get an infinite loop.

Graph = function () {}; Graph.extend = myExtendFunction; Graph.prototype = { generateScale: function () { //do stuff } } // base class defined elsewhere UsageGraph = Graph.extend({ generateScale: function () { this.constructor._super.generateScale.call(this); // run the parent method //do additional stuff } }) ExcessiveUsageGraph = Graph.extend({ // some methods, not including generateScale, which is inherited directly from Usage Graph }) var EUG = new ExcessiveUsageGraph(); EUG.generateScale(); // infinite loop 

The loop happens because ExcessiveUsageGraph extends the prototype chain to UsageGraph to start the method, but this is still set to the ExcessiveUsageGraph instance, so when I use this.constructor._super to start the parent method also goes one step up the chain to UsageGraph and calls the same method again.

How can I refer to parent methods from a prototype of type Backbone and avoid this type of loop. I also want to avoid calling parent classes by name, if possible.

edit Here is a fiddle demonstrating that this is happening in Backbone

+6
javascript inheritance prototypal-inheritance
source share
3 answers

You are using one of the limitations of JavaScript this and prototype inheritance, directly because you are trying to create an inheritance schema class in a language that does not support it directly.

Even with Backbone, you are usually not recommended to use "super" directly because of the limitations you have outlined, etc.

Problem fixing

A common solution is to call your prototype object directly, instead of trying to disguise it using a "super" link.

 UsageGraph = Graph.extend({ generateScale: function () { Graph.prototype.generateScale.call(this); // run the parent method //do additional stuff } }) 

In working JSFiddle: http://jsfiddle.net/derickbailey/vjvHP/4/

The reason this works is due to "this" in JavaScript. When you call a function, the keyword "this" is set depending on how you call the function, and not where the function is defined.

In the case of the call to the "generateScale" method in this code, this is the designation point for calling the generateScale function, which sets the context. In other words, since the code reads prototype.generateScale , the function call context (the keyword "this") is set to the prototype object, which is the prototype of the Graph constructor function.

Since Graph.prototype now the context of the call to generateScale , this function will work with the expected context and behavior.

Why this.constructor. super failed

Conversely, when you made this.constructor._super.generateScale , you enabled JavaScript to skew the context in a way you did not expect because of the this keyword at the beginning.

This is the third level of your hierarchy that causes a problem with "this". You call EUG.generateScale , which explicitly sets this to the EUG instance. The prototype search for the generateScale method returns to the Graph prototype to invoke the method because the method was not found directly in the EUG instance.

But this already configured for an EUG instance, and a prototype JavaScript search matches this . So, when the UsageGraph generateScale prototype is called, this set to an EUG instance. Therefore, calling this.constructor.__super__ will be evaluated from the EUG instance and will look for the UsageGraph prototype as __super__ , which means that you are going to call the same method on the same object, in the same context. So an infinite loop.

The decision not to use this in prototypical searches. Use the named function and prototype directly, as I showed in the solution, and JSFiddle.

+12
source share

Others have already talked about the limitations of JavaScript "this", so I will not repeat this. However, technically it is possible to define "_super" that will follow the inheritance chain. Ember.js is an example of a library that does this very well. For example, in Ember.js you can do this:

 var Animal = Ember.Object.extend({ say: function (thing) { console.log(thing + ' animal'); } }); var Dog = Animal.extend({ say: function (thing) { this._super(thing + ' dog'); } }); var YoungDog = Dog.extend({ say: function (thing) { this._super(thing + ' young'); } }); var leo = YoungDog.create({ say: function () { this._super('leo'); } }); leo.say(); 

leo.say () will output to the console "leo young dog animal" because this._super points to a method of the parent object with the same name. To see how Amber does this, you can look at the Ember.wrap function in the Ember source code:

http://cloud.github.com/downloads/emberjs/ember.js/ember-0.9.6.js

Ember.wrap is the place where they wrap each method of the object so that this._super points to the right place. Perhaps you can take this idea from Amber?

+2
source share

My best solution so far, which feels terribly hacked and invulnerable, is to call a method function to be able to reference it directly and compare it with the intended β€œparent” method - perhaps the first time I found use to provide methods of a single function name. Any comments or improvements are welcome;

 Graph = function () {}; Graph.extend = myExtendFunction; Graph.prototype = { generateScale: function GS() { //do stuff } } // base class defined elsewhere UsageGraph = Graph.extend({ generateScale: function GS() { var parentMethod = this.constructor._super.generateScale; if(parentMethod === GS) { parentMethod = this.constructor._super.constructor._super.generateScale; } parentMethod.call(this); // run the parent method //do additional stuff } }) ExcessiveUsageGraph = Graph.extend({ // some methods, not including generateScale, which is inherited directly from Usage Graph }) var EUG = new ExcessiveUsageGraph(); EUG.generateScale(); // infinite loop 
0
source share

All Articles