【问题标题】:Preventing infinite recursion when using Backbone-style prototypal inheritance使用主干式原型继承时防止无限递归
【发布时间】:2012-04-17 23:51:01
【问题描述】:

我正在使用从 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

【问题讨论】:

    标签: javascript inheritance backbone.js prototypal-inheritance


    【解决方案1】:

    其他人已经讲过 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 那里借用这个想法?

    【讨论】:

    • 这看起来很有趣,我会在开始我的下一个大项目之前检查 ember(我对 Backbone 很陌生,虽然我喜欢它,但它有很多挫折,所以我至少应该尝试一下另一个框架)。虽然将每个函数包装在另一个函数中(特别是当我目前在我的真实应用中有 4 层继承时)让我担心性能
    • 好消息约翰尼。我不知道 Ember 会这样做。虽然我必须指出 ember 跳过的箍来完成这项工作......它真的只是对我尖叫“不要这样做”。 :)
    • 是的,使这项工作的代码有点难看,但它确实允许您的库的使用者编写更清晰的代码。
    【解决方案2】:

    您遇到了 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.super 失败了

    相反,当您调用this.constructor._super.generateScale 时,您允许JavaScript 以您未预料到的方式扭曲上下文,因为一开始就使用了this 关键字。

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

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

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

    【讨论】:

    • 我认为您比我更好、更深入地解释了这种情况,但我已经发现这是问题所在,并且正在寻找一种避免直接引用父对象/类的解决方案。
    • 谢谢,祝你好运。我有一种感觉,当你继续前进时,你只会把头撞到更多的限制和问题上。 :)
    【解决方案3】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-09-03
      • 1970-01-01
      • 1970-01-01
      • 2013-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多