12

私はJavascriptプログラミングが初めてで、オブジェクト指向プログラミングの観点から最初のアプリケーション(実際にはゲーム)に取り組んでいます(jsが実際にはオブジェクト指向ではないことは知っていますが、この特定の問題については、次のように始める方が簡単でしたこれ)。

私は「クラス」の階層を持っており、最上位 (「モノ」クラス) が関連するもの (ゲーム内の添付アイテム) のリストを定義しています。ThingA1 および ThingA2 クラスによって継承される ThingA クラスによって継承されます。

最小限の例は次のようになります。

function Thing() 
{
  this.relatedThings   = [];
}
Thing.prototype.relateThing = function(what)
{
  this.relatedThings.push(what);
}

ThingA.prototype = new Thing();
ThingA.prototype.constructor = ThingA;
function ThingA()
{
}

ThingA1.prototype = new ThingA();
ThingA1.prototype.constructor = ThingA1;
function ThingA1()
{

}

ThingA2.prototype = new ThingA();
ThingA2.prototype.constructor = ThingA2;
function ThingA2()
{    
}

var thingList = [];

thingList.push(new ThingA());
thingList.push(new ThingA1());
thingList.push(new ThingA2());
thingList.push(new ThingA2());
thingList.push(new Thing());

thingList[1].relateThing('hello');

コードの最後で、relateThing が実行されると、すべての ThingA、ThingA1、および ThingA2 がそれを実行します (配列内の最後の「Thing」オブジェクトではありません)。ThingAプロトタイプでrelateThing関数を定義すると、正しく機能することがわかりました。ゲームがどのように設計されているかという理由で、私はそれをする必要がないことを好みます.

プロトタイプが JavaScript でどのように機能するかについて、私は何かを理解していないのかもしれません。関数がすべてのオブジェクト間で共有されていることは知っていますが、実行は個別になると思います。誰かがなぜこれが起こっているのか、そしてそれを解決する方法を説明できますか? 継承が間違っているのか、プロトタイプの定義が間違っているのか、それとも何なのかわかりません。

前もって感謝します。

4

3 に答える 3

23

プロトタイプチェーンへようこそ!

あなたの例でそれがどのように見えるか見てみましょう。

問題

を呼び出すと、配列を参照するnew Thing()プロパティを持つ新しいオブジェクトが作成されます。relatedThingsしたがって、次のように言えます。

+--------------+
|Thing instance|
|              |
| relatedThings|----> Array
+--------------+     

次に、このインスタンスを に割り当てますThingA.prototype:

+--------------+
|    ThingA    |      +--------------+
|              |      |Thing instance|
|   prototype  |----> |              |
+--------------+      | relatedThings|----> Array
                      +--------------+

したがって、の各インスタンスはインスタンスThingAから継承されThingます。ここで、新しいインスタンスを作成して各プロトタイプに割り当て、後で and のインスタンスを作成しますThingA1( andと、ただしここには示されていません)。ThingA2ThingAThingA1ThingA2ThingAThing

関係は次のようになりました (__proto__内部プロパティであり、オブジェクトとそのプロトタイプを接続します):

                               +-------------+
                               |   ThingA    |
                               |             |    
+-------------+                |  prototype  |----+
|   ThingA1   |                +-------------+    |
|             |                                   |
|  prototype  |---> +--------------+              |
+-------------+     |    ThingA    |              |
                    | instance (1) |              |
                    |              |              |
+-------------+     |  __proto__   |--------------+ 
|   ThingA1   |     +--------------+              |
|   instance  |           ^                       |
|             |           |                       v
|  __proto__  |-----------+                 +--------------+
+-------------+                             |Thing instance|
                                            |              |
                                            | relatedThings|---> Array
+-------------+     +--------------+        +--------------+ 
|   ThingA2   |     |   ThingA     |              ^
|             |     | instance (2) |              |
|  prototype  |---> |              |              |
+-------------+     |  __proto__   |--------------+
                    +--------------+
+-------------+           ^
|   ThingA2   |           |  
|   instance  |           |
|             |           |
|  __proto__  |-----------+
+-------------+                        

ThingAそのため、ThingA1またはのすべてのインスタンスは、 ThingA2 1 つの同じ配列インスタンスを参照します

これはあなたが望むものではありません!


ソリューション

この問題を解決するには、「サブクラス」の各インスタンスに独自の relatedThingsプロパティが必要です。super()これは、他の言語での呼び出しと同様に、各子コンストラクターで親コンストラクターを呼び出すことで実現できます。

function ThingA() {
    Thing.call(this);
}

function ThingA1() {
    ThingA.call(this);
}

// ...

これは、これらの関数内で and を呼び出しThing、に渡す最初の引数にThingA設定します。[MDN][MDN ]の詳細をご覧ください。this.call.call this

これだけで、上の図が次のように変わります。

                               +-------------+
                               |   ThingA    |
                               |             |    
+-------------+                |  prototype  |----+
|   ThingA1   |                +-------------+    |
|             |                                   |
|  prototype  |---> +--------------+              |
+-------------+     |    ThingA    |              |
                    | instance (1) |              |
                    |              |              |
                    | relatedThings|---> Array    |
+-------------+     |  __proto__   |--------------+ 
|   ThingA1   |     +--------------+              |
|   instance  |           ^                       |
|             |           |                       |
|relatedThings|---> Array |                       v
|  __proto__  |-----------+                 +--------------+
+-------------+                             |Thing instance|
                                            |              |
                                            | relatedThings|---> Array
+-------------+     +--------------+        +--------------+ 
|   ThingA2   |     |   ThingA     |              ^
|             |     | instance (2) |              |
|  prototype  |---> |              |              |
+-------------+     | relatedThings|---> Array    |
                    |  __proto__   |--------------+
                    +--------------+
+-------------+           ^
|   ThingA2   |           |  
|   instance  |           |
|             |           |
|relatedThings|---> Array | 
|  __proto__  |-----------+
+-------------+

ご覧のとおり、各インスタンスには独自のrelatedThingsプロパティがあり、異なる配列インスタンスを参照しています。プロトタイプ チェーンにはまだrelatedThingsプロパティがありますが、それらはすべてインスタンス プロパティによって隠されています。


より良い継承

また、プロトタイプを次のように設定しないでください。

ThingA.prototype = new Thing();

Thing実際には、ここで新しいインスタンスを作成する必要はありません。Thing予想される引数の場合はどうなりますか? あなたはどちらに合格しますか?コンストラクターの呼び出しThingに副作用がある場合はどうなりますか?

実際に必要なのはThing.prototype、プロトタイプ チェーンに接続することです。Object.create [MDN]でこれを行うことができます:

ThingA.prototype = Object.create(Thing.prototype);

コンストラクター ( Thing) が実行されたときに発生することはすべて、後で実際に新しいインスタンスを作成するときに発生します (上記のようThingAに呼び出すことによって)。Thing.call(this)

于 2013-02-23T13:55:56.400 に答える
1

継承と OOP の単純な方法を実現するために JavaScript でプロトタイピングが機能する方法が気に入らない場合は、これを参照することをお勧めします: https://github.com/haroldiedema/joii

基本的に、次のこと(およびそれ以上)を行うことができます。

// First (bottom level)
var Person = new Class(function() {
    this.name = "Unknown Person";
});

// Employee, extend on Person & apply the Role property.
var Employee = new Class({ extends: Person }, function() {
    this.name = 'Unknown Employee';
    this.role = 'Employee';
});

// 3rd level, extend on Employee. Modify existing properties.
var Manager = new Class({ extends: Employee }, function() {

    // Overwrite the value of 'role'.
    this.role = 'Manager';

    // Class constructor to apply the given 'name' value.
    this.__construct = function(name) {
        this.name = name;
    }
});

// And to use the final result:
var myManager = new Manager("John Smith");
console.log( myManager.name ); // John Smith
console.log( myManager.role ); // Manager
于 2014-01-22T16:19:11.650 に答える
0

In OOP, when you're are defining a constructor of a sub class you are also (implicitly or explicitly) choosing a constructor from the super type. When a sub object is constructed both constructor are executed, first that from the super class, the all the other down the hierarchy.

In javascript this must be explicitly called and don't come automatically!

function Thing() {
  this.relatedThings = [];
}
Thing.prototype.relateThing = function(what){
  this.relatedThings.push(what);
}

function ThingA(){
  Thing.call(this);
}
ThingA.prototype = new Thing();

function ThingA1(){
  ThingA.call(this);
}
ThingA1.prototype = new ThingA();

function ThingA2(){
  ThingA.call(this);

}
ThingA2.prototype = new ThingA();

If you don't this way, all the instance of ThingA, ThingA1 and ThingA2 shere the same relatedThings array constructed in the Thing constructor and called once for all instance at line:

ThingA.prototype = new Thing();

Globally, infact, in your code, you have only 2 calls to the Thing constructor and this result in only 2 instances of the relatedThings array.

于 2013-02-23T14:05:19.700 に答える