我正在使用从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
答案 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._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