6

我正在使用从 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 样式的原型中引用父方法并避免这种循环。如果可能,我还想避免按名称引用父类。

编辑 这是一个小提琴,证明这发生在 Backbone

4

3 回答 3

12

您遇到了 JavaScript 和原型继承的限制之一this,这完全是因为您试图用一种不直接支持它的语言创建类类继承方案。

即使使用 Backbone,由于您概述的限制等等,通常不鼓励您直接使用“super”。

解决问题

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


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的关键字,您允许 JavaScript 以您没有预料到的方式倾斜上下文。this

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

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

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

于 2012-04-04T13:52:01.053 回答
2

其他人已经讲过 JavaScript 的“this”的局限性,我不再赘述。但是,在技术上可以定义一个尊重继承链的“_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 指向正确的位置。也许你可以从 Ember 那里借用这个想法?

于 2012-04-04T15:28:31.010 回答
0

到目前为止,我最好的解决方案是给方法的函数命名,以便能够直接引用它并将其与假定的“父”方法进行比较——这可能是我第一次发现用于给方法一个单独的函数名。欢迎任何意见或改进;

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
于 2012-04-04T10:06:02.443 回答