使用Backbone样式的原型继承时防止无限递归

时间:2012-04-04 09:19:00

标签: javascript inheritance backbone.js prototypal-inheritance

我正在使用从Backbone改编的扩展功能(除了一些改变以符合我雇主的命名惯例)以实现原型继承。在设置了以下结构(下面简化得很简单)后,我得到了一个无限循环。

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

循环正在发生,因为ExcessiveUsageGraph将原型链上升到UsageGraph以运行该方法,但this仍设置为ExcessiveUsageGraph的实例,所以当我使用this.constructor._super来运行父方法,它也会向链的一步提升到UsageGraph并再次调用相同的方法。

如何从Backbone样式的原型中引用父方法并避免这种循环。我还想避免在可能的情况下按名称引用父类。

编辑 Here's a fiddle demonstrating that this happens in Backbone

3 个答案:

答案 0 :(得分:12)

您正在遇到JavaScript的this和原型继承的一个限制,正是因为您试图使用不直接支持它的语言创建类类继承方案。 / p>

即使使用Backbone,由于您已经概述的限制等原因,通常不鼓励您直接使用“超级”。

解决问题

常见的解决方案是直接调用原型对象,而不是试图通过使用“超级”引用来掩盖它。


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

在一个有效的JSFiddle中:http://jsfiddle.net/derickbailey/vjvHP/4/

这个工作的原因与JavaScript中的“this”有关。调用函数时,“this”关键字是根据调用函数的方式设置的,而不是函数定义的位置。

在此代码中调用“generateScale”方法的情况下,它是调用设置上下文的generateScale函数的点符号。换句话说,因为代码读取prototype.generateScale,函数调用的上下文(“this”关键字)被设置为prototype对象,恰好是Graph的原型构造函数。

由于Graph.prototype现在是generateScale调用的上下文,因此该函数将运行您期望的上下文和行为。

为什么this.constructor。超级失败

相反,当您调用this.constructor._super.generateScale时,由于开头的this关键字,您允许JavaScript以您不期望的方式扭曲上下文。

这是您的层次结构的第3级导致“this”问题。您正在调用EUG.generateScale,这是明确将this设置为EUG实例。 generateScale方法的原型查找返回到Graph原型以调用方法,因为直接在EUG实例上找不到该方法。

this已设置为EUG实例,而JavaScript的原型查找则尊重this。因此,当调用UsageGraph原型generateScale时,this将设置为EUG实例。因此,调用this.constructor.__super__将从EUG实例进行评估,并将找到UsageGraph原型作为__super__的值,这意味着您将调用相同的方法在同一个对象上,再次使用相同的上下文。因此,无限循环。

解决方案是不要在原型查找中使用this。直接使用命名函数和原型,正如我在解决方案和JSFiddle中所示。

答案 1 :(得分:2)

其他人已经谈过JavaScript的“这个”的局限性,所以我不再重复了。但是,在技术上可以定义一个尊重继承链的“_super”。 Ember.js是一个非常好的图书馆的例子。例如,在Ember.js中,您可以这样做:

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()会将“leo young dog animal”输出到控制台,因为this._super指向其父对象的同名方法。要了解Ember是如何做到这一点的,您可以在Ember的源代码中查看Ember.wrap函数:

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

Ember.wrap是它们包装对象的每个方法的地方,以便this._super指向正确的位置。也许你可以从恩伯恩那里借用这个想法?

答案 2 :(得分:0)

到目前为止,我最好的解决方案,感觉非常hacky和不可扩展,是命名方法的功能,以便能够直接引用它并将其与所谓的“父”方法进行比较 - 也许是我第一次找到用于为方法提供单独的函数名称。欢迎任何评论或改进;

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