web-dev-qa-db-ja.com

JavaScriptのプロトタイプ継承を理解する

JavaScript OOPは初めてです。次のコードブロックの違いについて説明してください。私はテストし、両方のブロックが動作します。ベストプラクティスとその理由は何ですか?

最初のブロック:

_function Car(name){
    this.Name = name;
}

Car.prototype.Drive = function(){
    console.log("My name is " + this.Name + " and I'm driving.");
}

SuperCar.prototype = new Car();
SuperCar.prototype.constructor = SuperCar;

function SuperCar(name){
    Car.call(this, name);
}

SuperCar.prototype.Fly = function(){
    console.log("My name is " + this.Name + " and I'm flying!");
}

var myCar = new Car("Car");
myCar.Drive();

var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();_

2番目のブロック:

_function Car(name){
    this.Name = name;
    this.Drive = function(){ 
        console.log("My name is " + this.Name + " and I'm driving.");
    }
}

SuperCar.prototype = new Car();

function SuperCar(name){
    Car.call(this, name);
    this.Fly = function(){
        console.log("My name is " + this.Name + " and I'm flying!");
    }
}

var myCar = new Car("Car");
myCar.Drive();

var mySuperCar = new SuperCar("SuperCar");
mySuperCar.Drive();
mySuperCar.Fly();_

著者がDriveを使用してFlyおよびprototypeメソッドを追加し、Carクラス内で_this.Drive_メソッドとして宣言しなかった理由SuperCarクラスの_this.Fly_として?

なぜ_SuperCar.prototype.constructor_をSuperCarに戻す必要があるのですか? constructorが設定されている場合、prototypeプロパティはオーバーライドされますか?私はこの行をコメントアウトしましたが、何も変わりませんでした。

SuperCarコンストラクターでCar.call(this, name);を呼び出すのはなぜですか? Carのプロパティとメソッドが「継承」されない

_var myCar = new Car("Car");
_
170
Dasha Salo

2つのブロックは、最初の例ではDrive()が1回だけ存在し、2番目のアプローチではDrive()がインスタンスごとに存在するという点で異なります(new Car()drive()が再度作成されます)。または、最初の関数はプロトタイプを使用して関数を保存し、2番目の関数はコンストラクターを使用するという。関数のルックアップはコンストラクターであり、プロトタイプです。したがって、Drive()のルックアップでは、コンストラクター内にあるかプロトタイプ内にあるかに関係なく検索されます。通常、必要なのは型ごとに1回だけなので、プロトタイプの使用はより効率的です。

JavaScriptのnew呼び出しは、プロトタイプのコンストラクターを自動的に設定します。プロトタイプを上書きする場合は、コンストラクターを手動で設定する必要があります。

Javascriptの継承には、superのようなものはありません。したがって、サブクラスがある場合、スーパーコンストラクターを呼び出す唯一の機会はその名前です。

82
Norbert Hartl

Norbert Hartl's answer に追加するには、SuperCar.prototype.constructorは必要ありませんが、一部の人々はオブジェクト(この場合はSuperCarオブジェクト)。

最初の例から、Car.call(this、name)はSuperCarコンストラクター関数にあります。これを行うとき:

_var mySuperCar = new SuperCar("SuperCar");
_

これがJavaScriptの機能です。

  1. 新鮮な空のオブジェクトがインスタンス化されます。
  2. 新しいオブジェクトの内部プロトタイプはCarに設定されます。
  3. SuperCarコンストラクター関数が実行されます。
  4. 完成したオブジェクトが返され、mySuperCarに設定されます。

JavaScriptがCarを呼び出していないことに注意してください。プロトタイプはそのままで、SuperCar用に自分で設定していないプロパティやメソッドはすべてCarで検索されます。時々これは良いです、例えばSuperCarにはDriveメソッドはありませんが、Carのメソッドを共有できるため、すべてのSuperCarは同じDriveメソッドを使用します。他の場合は、各スーパーカーが独自の名前を持っているなど、共有したくない場合があります。それでは、各スーパーカーの名前をそれ自身の名前に設定するにはどうすればよいのでしょうか? SuperCarコンストラクター関数内でthis.Nameを設定できます。

_function SuperCar(name){
    this.Name = name;
}
_

これは機能しますが、しばらくお待ちください。 Carコンストラクターでまったく同じことをしませんでしたか?繰り返したくありません。 Carはすでに名前を設定しているので、名前を付けましょう。

_function SuperCar(name){
    this = Car(name);
}
_

おっと、特別なthisオブジェクト参照を変更する必要はありません。 4つのステップを覚えていますか? JavaScriptが提供するオブジェクトにつかまってください。SuperCarオブジェクトとCarの間の貴重な内部プロトタイプリンクを保持する唯一の方法だからです。では、どうすればNameを設定できますか。自分自身を繰り返したり、新しいSuperCarオブジェクトJavaScriptを捨てたりせずに、JavaScriptが準備に多大な労力を費やしたのでしょうか。

二つのこと。 1つ:thisの意味は柔軟です。 2:車は機能です。 Carを呼び出すには、純粋で新鮮なインスタンス化されたオブジェクトではなく、たとえばSuperCarオブジェクトを使用します。これで最終的な解決策が得られます。これは、質問の最初の例の一部です。

_function SuperCar(name){
    Car.call(this, name);
}
_

関数として、Carは関数の callメソッド で呼び出すことができます。これは、Car内のthisの意味をSuperCarに変更します構築中のインスタンス。プレスト!各SuperCarは、独自のNameプロパティを取得するようになりました。

最後に、SuperCarコンストラクターのCar.call(this, name)は、それぞれの新しいSuperCarオブジェクトに固有のNameプロパティを与えますが、既にCarにあるコードを複製することはありません。

プロトタイプは一度理解すれば怖くはありませんが、古典的なクラス/継承OOPモデルではありません。私は に関する記事を書きました。 JavaScriptのプロトタイプコンセプト JavaScriptを使用するゲームエンジン用に記述されていますが、Firefoxで使用されるJavaScriptエンジンと同じであるため、すべて関連性があります。

140
Tung Nguyen

ノーバート、あなたはあなたの最初の例がダグラス・クロックフォードが疑似古典的継承と呼ぶものとほとんど同じであることに注意すべきです。これについて注意すべきこと:

  1. Carコンストラクターを2回呼び出します。1回はSuperCar.prototype = new Car()行から、もう1つは「constructor stealing」行からCar.call(this ...代わりにプロトタイプを継承するヘルパーメソッドを作成し、車のコンストラクターは、セットアップをより効率的にするために1回実行するだけで済みます。
  2. SuperCar.prototype.constructor = SuperCar行により、instanceofを使用してコンストラクターを識別できます。一部の人々はこれを望みます
  3. 次のような参照変数:var arr = ['one'、 'two']は、スーパー(Carなど)で定義されている場合、すべてのインスタンスで共有されます。これは、inst1.arr.Push ['three']、inst2.arr.Push ['four']などがすべてのインスタンスで表示されることを意味します!基本的に、おそらく望まない静的な動作です。
  4. 2番目のブロックは、コンストラクターでflyメソッドを定義します。つまり、呼び出されるたびに、「メソッドオブジェクト」が作成されます。メソッドのプロトタイプを使用する方が良い!ただし、必要に応じてコンストラクタ内に保持することができます-プロトタイプリテラルを一度だけ初期化する(擬似)ためにガードする必要があるだけです:if(SuperCar.prototype.myMethod!= 'function')... then defineプロトタイプリテラル。
  5. 「Car.call(this、name)....を呼び出す理由」:コードを注意深く見る時間がないので間違っているかもしれませんが、これは通常、各インスタンスが修正するために独自の状態を維持できるようにするためです。上記で説明したプロトタイプチェーンの「静的な」動作の問題。

最後に、ここで機能するTDD JavaScript継承コードの例をいくつか紹介します。 TDD JavaScript継承コードとエッセイ 改善を望んでいるので、フィードバックをもらいたいですそれをオープンソースにしてください。目標は、古典的なプログラマーがJavaScriptをすばやく習得できるようにすることと、Crockfordの本とZakasの本の両方を学習することです。

8
Rob
function abc() {
}

関数abc用に作成されたプロトタイプメソッドとプロパティ

abc.prototype.testProperty = 'Hi, I am prototype property';
abc.prototype.testMethod = function() { 
   alert('Hi i am prototype method')
}

関数abcの新しいインスタンスを作成する

var objx = new abc();

console.log(objx.testProperty); // will display Hi, I am prototype property
objx.testMethod();// alert Hi i am prototype method

var objy = new abc();

console.log(objy.testProperty); //will display Hi, I am prototype property
objy.testProperty = Hi, I am over-ridden prototype property

console.log(objy.testProperty); //will display Hi, I am over-ridden prototype property

http://astutejs.blogspot.in/2015/10/javascript-prototype-is-easy.html

1
Jack Sane

100%確かではありませんが、違いは、2番目の例ではCarクラスの内容を単にSuperCarオブジェクトに複製し、最初の例ではSuperCarプロトタイプをCarクラスにリンクすることです。 CarクラスはSuperCarクラスにも影響します。

1
Jeff Ober