I'm using an extend function adapted from Backbone (identical apart from a few changes to comply with my employer's naming conventions) to implement prototypal 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's 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 is happening because ExcessiveUsageGraph
goes up the prototype chain to UsageGraph
to run the method, but this
is still set to an instance of ExcessiveUsageGraph
so when I use this.constructor._super
to run the parent method it also goes one step up the chain to UsageGraph
and calls the same method again.
How can I reference parent methods from within a Backbone-style prototype and avoid this kind of loop. I also want to avoid referring to parent classes by name if possible.
edit Here's a fiddle demonstrating that this happens in Backbone
You're running in to one of the limitations of JavaScript's this
and prototypal inheritance, squarely because you're attempting to create a class-like inheritance scheme in a language that doesn't directly support it.
Even with Backbone, you are generally discouraged from using "super" directly because of the limitations that you've outlined, and more.
Fixing the problem
The common solution is to call your prototype object directly, instead of trying to mask it through the use of a "super" reference.
UsageGraph = Graph.extend({
generateScale: function () {
Graph.prototype.generateScale.call(this); // run the parent's method
//do additional stuff
}
})
In a working JSFiddle: http://jsfiddle.net/derickbailey/vjvHP/4/
The reason this works has to do with "this" in JavaScript. When you call a function, the "this" keyword is set based on how you call the function, not where the function is defined.
In the case of calling the "generateScale" method in this code, it's the dot-notation of invoking the generateScale function that sets the context. In other words, because the code reads prototype.generateScale
, the context of the function call (the "this" keyword) is set to the prototype
object, which happens to be the prototype of the Graph
constructor function.
Since the Graph.prototype
is now the context of the call to generateScale
, that function will run with the context and behavior that you are expecting.
Why this.constructor.super failed
Conversely, when you made the call to this.constructor._super.generateScale
, you allowed JavaScript to skew the context in a manner that you didn't expect because of the this
keyword at the start.
It's the 3rd level of your hierarchy that's causing the problem with "this". You're calling EUG.generateScale
, which is explicitly setting this
to the EUG
instance. The prototypal lookup for the generateScale
method reaches back to the Graph
prototype to call the method, because the method is not found on the EUG
instance directly.
But this
has already been set to the EUG
instance, and JavaScript's prototypal lookup respects this
. So, when the UsageGraph prototype generateScale
is called, this
is set to the EUG
instance. Therefore, calling this.constructor.__super__
is going to be evaluated from the EUG
instance and is going to find the UsageGraph prototype as the value of __super__
, which means you're going to call the same method on the same object, with the same context again. Thus, an infinite loop.
The solution is not to use this
in prototypal lookups. Use the named function and prototype directly, as I showed in the solution and JSFiddle.
Others have already talked about the limitations of JavaScript's "this", so I won't repeat that. However, it is technically possible to to define a "_super" that will honor the inheritance chain. Ember.js is an example of a library that does this really 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 "leo young dog animal" to the console because this._super points to its parent object's method of the same name. To see how Ember is doing this, you can have a look at the Ember.wrap function in Ember's source code here:
http://cloud.github.com/downloads/emberjs/ember.js/ember-0.9.6.js
Ember.wrap is where they wrap every method of an object so that this._super points to the right place. Perhaps you can borrow this idea from Ember?
My best solution so far, which feels dreadfully hacky and unscaleable, is to name the method's function in order to be able to reference it directly and compare it with the supposed "parent" method - maybe the first time I've found a use for giving methods a separate function name. Any comments or improvements 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's 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
来源:https://stackoverflow.com/questions/10008285/preventing-infinite-recursion-when-using-backbone-style-prototypal-inheritance