Prévenir la récursivité infinie lors de l'utilisation de l'héritage prototypique de style Backbone

J'utilise une fonction étendue adaptée de Backbone (identique à partir de quelques modifications pour respecter les conventions de dénomination de mon employeur) pour mettre en œuvre l'héritage prototypique. Après avoir configuré la structure suivante (beaucoup simplifié ci-dessous), j'ai une boucle infinie.

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 

La boucle se produit car ExcessiveUsageGraph monte la chaîne prototype à UsageGraph pour exécuter la méthode, mais this est toujours définie sur une instance de ExcessiveUsageGraph alors lorsque j'utilise this.constructor._super pour exécuter la méthode parent, elle passe également un pas vers le haut de la chaîne À UsageGraph et appelle à nouveau la même méthode.

Comment puis-je faire référence aux méthodes parent dans un prototype de type Backbone et éviter ce type de boucle. Je souhaite également éviter de faire référence aux classes parentales si possible.

Edit Voici un violon démontrant que cela se produit dans Backbone

Vous exécutez l'une des limites de l'héritage prototype et de JavaScript de JavaScript, car vous essayez de créer un schéma d'héritage de classe dans une langue qui ne l'accompagne pas directement.

Même avec Backbone, vous êtes généralement découragé d'utiliser «super» directement en raison des limitations que vous avez décrites, et plus encore.

Réparer le problème

La solution commune consiste à appeler directement votre prototype d'objet, au lieu d'essayer de le masquer par l'utilisation d'une référence "super".

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

Dans un laboratoire JSFiddle: http://jsfiddle.net/derickbailey/vjvHP/4/

La raison pour laquelle cela fonctionne a trait à "ceci" en JavaScript. Lorsque vous appelez une fonction, le mot-clé "this" est défini en fonction de la façon dont vous appelez la fonction, pas où la fonction est définie.

Dans le cas d'appeler la méthode "generateScale" dans ce code, c'est la notation des points d'appel de la fonction generateScale qui définit le contexte. En d'autres termes, parce que le code lit prototype.generateScale , le contexte de l'appel de fonction (le mot-clé "this") est défini sur l'objet prototype , qui constitue le prototype de la fonction de constructeur Graph .

Étant donné que Graph.prototype est maintenant le contexte de l'appel à Graph.prototype , cette fonction fonctionnera avec le contexte et le comportement que vous attendez.

Pourquoi ce constructeur. Super échoué

À l'inverse, lorsque vous avez téléphoné à this.constructor._super.generateScale , vous avez autorisé JavaScript à incliner le contexte d'une manière que vous ne vous attendiez pas à this mot this clé au début.

C'est le 3ème niveau de votre hiérarchie qui cause le problème avec "ceci". Vous appelez EUG.generateScale , ce qui définit explicitement this dans l'instance EUG . La recherche prototypique pour la méthode generateScale revient au prototype Graph pour appeler la méthode, car la méthode n'est pas trouvée sur l'instance EUG directement.

Mais this a déjà été défini dans l'instance EUG , et la recherche prototypique de JavaScript respecte this . Ainsi, lorsque le prototype UsageGraph generateScale est appelé, this est défini sur l'instance EUG . Par conséquent, appeler ce this.constructor.__super__ va être évalué à partir de l'instance EUG et va trouver le prototype UsageGraph comme valeur de __super__ , ce qui signifie que vous allez appeler la même méthode sur le même objet, avec le même Contexte à nouveau. Ainsi, une boucle infinie.

La solution n'est pas de l'utiliser dans les recherches prototypes. Utilisez la fonction nommée et le prototype directement, comme je l'ai montré dans la solution et JSFiddle.

D'autres ont déjà parlé des limitations de JavaScript de "ceci", donc je ne vais pas le répéter. Cependant, il est techniquement possible de définir un «_super» qui honorera la chaîne d'héritage. Ember.js est un exemple d'une bibliothèque qui fait très bien. Par exemple, dans Ember.js, vous pouvez le faire:

 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 () affichera "leo young dog animal" sur la console car this._super indique la méthode de son objet parent du même nom. Pour voir comment Ember fait cela, vous pouvez regarder la fonction Ember.wrap dans le code source d'Ember ici:

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

Ember.wrap est l'endroit où ils enveloppent toutes les méthodes d'un objet afin que this._super pointe au bon endroit. Peut-être que vous pouvez emprunter cette idée à Ember?

Ma meilleure solution jusqu'à présent, qui se sent terriblement piquante et non balayable, est de nommer la fonction de la méthode afin de pouvoir la référencer directement et de la comparer à la méthode supposée "parent" – peut-être la première fois que j'ai trouvé une utilisation pour En donnant aux méthodes un nom de fonction distinct. Tous les commentaires ou améliorations sont bienvenus;

 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