6

プロトタイプの継承を実装するために、バックボーン (雇用主の命名規則に準拠するためのいくつかの変更を除いて同一) から適用された拡張関数を使用しています。次の構造を設定した後 (以下では非常に簡略化されています)、無限ループが発生します。

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にプロトタイプチェーンを上るが、まだインスタンスに設定されているため、親メソッドを実行するために使用すると、チェーンを1ステップ上にして同じメソッドを再度呼び出すためです。UsageGraphthisExcessiveUsageGraphthis.constructor._superUsageGraph

Backbone スタイルのプロトタイプ内から親メソッドを参照して、この種のループを回避するにはどうすればよいですか。また、可能であれば、親クラスを名前で参照することも避けたいと考えています。

編集 これがバックボーンで発生することを示すフィドルです

4

3 に答える 3

12

this直接サポートしていない言語でクラスのような継承スキームを作成しようとしているため、JavaScript とプロトタイプの継承の制限の 1 つに直面しています。

バックボーンを使用しても、概説した制限などのために、「スーパー」を直接使用することは一般的にお勧めできません。

問題の修正

一般的な解決策は、「スーパー」参照を使用してプロトタイプ オブジェクトをマスクするのではなく、プロトタイプ オブジェクトを直接呼び出すことです。


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

「これ」で問題を引き起こしているのは、階層の第 3 レベルです。EUG.generateScaleインスタンスに明示的に設定thisしている を呼び出していEUGます。メソッドのプロトタイプ ルックアップは、メソッドを呼び出すgenerateScaleためにプロトタイプに戻ります。これは、メソッドがインスタンスで直接Graph見つからないためです。EUG

しかしthis、すでにEUGインスタンスに設定されており、JavaScript のプロトタイプのルックアップはthis. そのため、UsageGraph プロトタイプgenerateScaleが呼び出されるとthis、インスタンスに設定され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 に答える