それで、私はついにこれらの年の間ずっと足を引きずることをやめて、JavaScriptを「適切に」学ぶことを決めました。言語設計の最も頭を悩ます要素の1つは、継承の実装です。Rubyの経験があったので、クロージャーと動的型付けを見て本当に幸せでした。しかし、私の人生では、継承のために他のインスタンスを使用するオブジェクトインスタンスからどのようなメリットが得られるかを理解できません。
それで、私はついにこれらの年の間ずっと足を引きずることをやめて、JavaScriptを「適切に」学ぶことを決めました。言語設計の最も頭を悩ます要素の1つは、継承の実装です。Rubyの経験があったので、クロージャーと動的型付けを見て本当に幸せでした。しかし、私の人生では、継承のために他のインスタンスを使用するオブジェクトインスタンスからどのようなメリットが得られるかを理解できません。
回答:
この回答は3年遅れであることは知っていますが、現在の回答では、プロトタイプの継承がクラシックの継承よりも優れていることについて十分な情報が得られないと思います。
最初に、JavaScriptプログラマーがプロトタイプ継承を擁護して述べる最も一般的な引数を見てみましょう(これらの引数は、現在の回答のプールから取っています)。
現在、これらの議論はすべて有効ですが、誰もその理由を説明することに煩わされていません。数学を学ぶことが重要だと子供に言うようなものです。確かにそうですが、子供は確かに気にしません。そして、それが重要だと言って数学のような子供を作ることはできません。
プロトタイプ継承の問題は、JavaScriptの観点から説明されていることだと思います。私はJavaScriptを愛していますが、JavaScriptのプロトタイプ継承は間違っています。従来の継承とは異なり、プロトタイプ継承には2つのパターンがあります。
残念ながらJavaScriptはプロトタイプ継承のコンストラクタパターンを使用しています。これは、JavaScriptが作成されたときに、JSの作成者であるBrendan EichがそれをJava(従来の継承を持つ)のように見せたかったためです。
そして、Visual Basicのような補完言語は、当時のMicrosoftの言語ファミリのC ++であったため、Javaの弟としてそれを推進していました。
JavaScriptでコンストラクターを使用する場合、他のコンストラクターから継承するコンストラクターを考えるため、これは悪いことです。これは間違っています。プロトタイプの継承では、オブジェクトは他のオブジェクトから継承します。コンストラクターは決して登場しません。これはほとんどの人を混乱させるものです。
伝統的な継承を持つJavaのような言語の人々は、コンストラクターはクラスのように見えますが、クラスのようには動作しないため、さらに混乱します。ダグラス・クロックフォードは、次のように述べました。
この間接参照は、古典的に訓練されたプログラマにとって言語をより親しみやすくするためのものでしたが、JavaプログラマがJavaScriptを持っているという非常に低い意見からわかるように、そうすることはできませんでした。JavaScriptのコンストラクターパターンは、古典的な人々には魅力的ではありませんでした。また、JavaScriptの真のプロトタイプの性質も覆い隠しました。その結果、言語を効果的に使用する方法を知っているプログラマーはほとんどいません。
そこにあります。馬の口から真っ直ぐに。
プロトタイプの継承はすべてオブジェクトに関するものです。オブジェクトは他のオブジェクトからプロパティを継承します。これですべてです。プロトタイプ継承を使用してオブジェクトを作成するには、2つの方法があります。
注: JavaScriptでは、オブジェクトのクローンを作成する2つの方法(委任と連結)が提供されています。以降では、「クローン」という語を委任による継承のみに言及するために使用し、「コピー」という語を連結による継承に排他的に言及するために使用します。
十分な話。いくつかの例を見てみましょう。半径の円があるとしましょう5
:
var circle = {
radius: 5
};
半径から円の面積と円周を計算できます:
circle.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
circle.circumference = function () {
return 2 * Math.PI * this.radius;
};
次に、別の半径の円を作成します10
。これを行う1つの方法は次のとおりです。
var circle2 = {
radius: 10,
area: circle.area,
circumference: circle.circumference
};
ただし、JavaScriptはより良い方法委譲を提供します。Object.create
関数は、これを実行するために使用されます。
var circle2 = Object.create(circle);
circle2.radius = 10;
それで全部です。JavaScriptでプロトタイプの継承を行いました。簡単ではなかったですか?オブジェクトを取得し、それを複製し、必要なものをすべて変更し、プレスト-自分で新しいオブジェクトを取得します。
「これは簡単ですか?新しい円を作成したいときはいつでも、クローンを作成circle
して手動で半径を割り当てる必要があります」と尋ねるかもしれません。解決策は、関数を使用して重い作業を行うことです。
function createCircle(radius) {
var newCircle = Object.create(circle);
newCircle.radius = radius;
return newCircle;
}
var circle2 = createCircle(10);
実際、次のように、これらすべてを1つのオブジェクトリテラルに組み合わせることができます。
var circle = {
radius: 5,
create: function (radius) {
var circle = Object.create(this);
circle.radius = radius;
return circle;
},
area: function () {
var radius = this.radius;
return Math.PI * radius * radius;
},
circumference: function () {
return 2 * Math.PI * this.radius;
}
};
var circle2 = circle.create(10);
上記のプログラムで気付いた場合、create
関数はのクローンを作成し、それにcircle
新しいradius
を割り当ててから返します。これは、JavaScriptでのコンストラクターの動作とまったく同じです。
function Circle(radius) {
this.radius = radius;
}
Circle.prototype.area = function () {
var radius = this.radius;
return Math.PI * radius * radius;
};
Circle.prototype.circumference = function () {
return 2 * Math.PI * this.radius;
};
var circle = new Circle(5);
var circle2 = new Circle(10);
JavaScriptのコンストラクターパターンは、逆のプロトタイプパターンです。オブジェクトを作成する代わりに、コンストラクタを作成します。new
キーワードは、バインドthis
のクローンにコンストラクタの内部ポインタをprototype
コンストラクタの。
混乱しているように聞こえますか?これは、JavaScriptのコンストラクタパターンが不必要に複雑になっているためです。これは、ほとんどのプログラマが理解するのが難しいと感じていることです。
他のオブジェクトから継承するオブジェクトについて考える代わりに、他のコンストラクタから継承するコンストラクタについて考え、完全に混乱します。
JavaScriptのコンストラクターパターンを回避する必要がある理由は他にもたくさんあります。これらについては、私のブログ投稿(コンストラクタ対プロトタイプ)で読むことができます。
それでは、古典的継承よりもプロトタイプ継承の利点は何ですか?最も一般的な議論をもう一度見て、その理由を説明しましょう。
CMSは彼の答えで述べています:
私の意見では、プロトタイプ継承の主な利点はその単純さです。
今行ったことを考えてみましょう。circle
半径がのオブジェクトを作成しました5
。次に、クローンを作成し、クローンに半径を指定しました10
。
したがって、プロトタイプの継承を機能させるために必要なものは2つだけです。
Object.create
。対照的に、古典的な継承ははるかに複雑です。古典的な継承では、次のようになります。
あなたはアイデアを得ます。ポイントは、プロトタイプの継承が理解しやすく、実装しやすく、推論しやすいことです。
Steve Yeggeが彼の古典的なブログ投稿「Portrait of a N00b」でそれを述べているように:
メタデータは、何か別の種類の説明またはモデルです。コード内のコメントは、計算の自然言語による説明にすぎません。メタデータをメタデータにするのは、厳密には必要ないということです。血統書類のある犬を飼っていて、書類を紛失しても、完全に有効な犬がいます。
同じ意味で、クラスは単なるメタデータです。クラスは継承に厳密には必要ありません。ただし、一部の人々(通常はn00bs)は、クラスを使用するほうが快適であると感じています。それは彼らに誤った安心感を与えます。
まあ、私たちは静的型が単なるメタデータであることも知っています。これらは、プログラマとコンパイラの2種類の読者を対象とした特別な種類のコメントです。静的型は、おそらく両方の読者グループがプログラムの意図を理解するのに役立つように、計算についてのストーリーを伝えます。しかし、静的な型は実行時に捨てることができます。結局のところ、静的な型はコメントを様式化したものだからです。彼らは血統の書類のようなものです。それは彼らの犬についてある種の不安定な性格タイプを幸せにするかもしれませんが、犬は確かに気にしません。
先に述べたように、クラスは人々に誤った安心感を与えます。たとえばNullPointerException
、コードが完全に読みやすい場合でも、Javaでが多すぎます。私は古典的な継承が通常プログラミングの邪魔になると思いますが、多分それは単なるJavaです。Pythonには驚くべき古典的な継承システムがあります。
古典的なバックグラウンドを持つ出身のほとんどのプログラマーは、古典的な継承はプロトタイプの継承よりも強力であると主張しています。
この主張は誤りです。JavaScript がクロージャーを介してプライベート変数をサポートすることはすでに知っていますが、多重継承についてはどうでしょうか?JavaScriptのオブジェクトにはプロトタイプが1つだけあります。
真実は、プロトタイプの継承が複数のプロトタイプからの継承をサポートしていることです。プロトタイプの継承とは、あるオブジェクトが別のオブジェクトから継承することを意味します。プロトタイプ継承を実装するには、実際には2つの方法があります。
はいJavaScriptでは、オブジェクトは他の1つのオブジェクトにのみ委任できます。ただし、任意の数のオブジェクトのプロパティをコピーできます。たとえば_.extend
これだけです。
もちろん、多くのプログラマーはこれを真の継承であるinstanceof
とは考えていませんisPrototypeOf
。ただし、これは、連結によってプロトタイプから継承するすべてのオブジェクトにプロトタイプの配列を格納することで簡単に修正できます。
function copyOf(object, prototype) {
var prototypes = object.prototypes;
var prototypeOf = Object.isPrototypeOf;
return prototypes.indexOf(prototype) >= 0 ||
prototypes.some(prototypeOf, prototype);
}
したがって、プロトタイプの継承は、古典的な継承と同じくらい強力です。実際、プロトタイプの継承では、コピーするプロパティと異なるプロトタイプから除外するプロパティを手動で選択できるため、従来の継承よりもはるかに強力です。
従来の継承では、継承するプロパティを選択することは不可能(または少なくとも非常に困難)です。ダイヤモンドの問題を解決するために、仮想基本クラスとインターフェースを使用しています。
ただし、JavaScriptでは、どのプロパティを継承し、どのプロトタイプから継承するかを正確に制御できるため、ダイヤモンドの問題を聞くことはほとんどありません。
古典的な継承は必ずしもより冗長なコードにつながるわけではないので、この点を説明するのは少し難しいです。実際、古典的であれプロトタイプ的であれ、継承はコードの冗長性を減らすために使用されます。
1つの議論は、古典的な継承を持つほとんどのプログラミング言語は静的に型付けされ、ユーザーが明示的に型を宣言する必要があるということかもしれません(暗黙の静的型付けを持つHaskellとは異なります)。したがって、これはより冗長なコードにつながります。
Javaはこの動作で悪名高いです。私ははっきりと覚えているボブ・ナイストロームをについての彼のブログ記事で、次の逸話に言及プラットパーサ:
ここでは、Javaの「四重に署名してください」というレベルの官僚機構が好きです。
繰り返しになりますが、それは、Javaが非常に不便だからです。
有効な引数の1つは、従来の継承を持つすべての言語が多重継承をサポートしているわけではないということです。ここでもJavaが思い浮かびます。はいJavaにはインターフェースがありますが、それだけでは不十分です。時には、本当に多重継承が必要な場合があります。
プロトタイプの継承では複数の継承が可能であるため、古典的な継承はあるが複数の継承はない言語ではなく、プロトタイプの継承を使用して記述した場合、複数の継承を必要とするコードはそれほど冗長ではありません。
プロトタイプ継承の最も重要な利点の1つは、プロトタイプを作成した後に、プロトタイプに新しいプロパティを追加できることです。これにより、プロトタイプに委譲するすべてのオブジェクトが自動的に使用できるようになるプロトタイプに新しいメソッドを追加できます。
一度クラスを作成すると、実行時にそれを変更できないため、これは従来の継承では不可能です。これはおそらく、古典的継承に対するプロトタイプ継承の最大の利点の1つであり、それが最上位であるはずです。しかし、私は最後のために最高のものを保存するのが好きです。
プロトタイプの継承が重要です。JavaScriptプログラマーに、プロトタイプ継承のプロトタイプパターンを優先して、プロトタイプ継承のコンストラクターパターンを放棄する理由を説明することが重要です。
JavaScriptを正しく教えることから始める必要があります。つまり、コンストラクターパターンではなくプロトタイプパターンを使用してコードを記述する方法を新しいプログラマーに示す必要があります。
プロトタイプのパターンを使用してプロトタイプの継承を説明するのが簡単になるだけでなく、より優れたプログラマーになるでしょう。
この回答が気に入ったら、「プロトタイプの継承が重要である理由」に関する私のブログ投稿も読んでください。私を信じて、あなたは失望することはありません。
Object.create
指定されたプロトタイプで新しいオブジェクトを作成しています。あなたが選んだ言葉は、プロトタイプが複製されているような印象を与えます。
実際に質問にインラインで回答させてください。
プロトタイプの継承には、次の長所があります。
ただし、次の欠点があります。
上記の行の間を読んで、従来のクラス/オブジェクトスキームの対応する長所と短所を考え出すことができると思います。もちろん、各エリアにはもっと多くのエリアがあるので、残りは他の人に答えてもらいます。
IMOのプロトタイプ継承の主な利点は、その単純さです。
言語のプロトタイプの性質は、古典的に訓練された人々を混乱させる可能性がありますが、実際には、これは非常に単純で強力な概念である差分継承であることがわかります。
分類を行う必要はありません。コードは小さく、冗長性が低く、オブジェクトは他のより一般的なオブジェクトから継承されます。
プロトタイプで考えると、クラスが必要ないことにすぐに気付くでしょう...
近い将来、プロトタイプの継承がより一般的になるでしょう。ECMAScript5th Edition仕様でObject.create
メソッドが導入されました。これにより、別のインスタンスから継承する新しいオブジェクトインスタンスを非常に簡単な方法で作成できます。
var obj = Object.create(baseInstance);
この新しいバージョンの標準はすべてのブラウザーベンダーによって実装されており、より純粋なプロトタイプの継承が見られるようになると思います...
2つの方法のどちらを選択するかはそれほど多くありません。把握すべき基本的な考え方は、JavaScriptエンジンが読み取るオブジェクトのプロパティを与えられると、最初にインスタンスをチェックし、そのプロパティがない場合はプロトタイプチェーンをチェックするということです。次に、プロトタイプとクラシックの違いを示す例を示します。
プロトタイプ
var single = { status: "Single" },
princeWilliam = Object.create(single),
cliffRichard = Object.create(single);
console.log(Object.keys(princeWilliam).length); // 0
console.log(Object.keys(cliffRichard).length); // 0
// Marriage event occurs
princeWilliam.status = "Married";
console.log(Object.keys(princeWilliam).length); // 1 (New instance property)
console.log(Object.keys(cliffRichard).length); // 0 (Still refers to prototype)
インスタンスメソッドを使用したクラシック (各インスタンスは独自のプロパティを格納するため非効率的)
function Single() {
this.status = "Single";
}
var princeWilliam = new Single(),
cliffRichard = new Single();
console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 1
効率的なクラシック
function Single() {
}
Single.prototype.status = "Single";
var princeWilliam = new Single(),
cliffRichard = new Single();
princeWilliam.status = "Married";
console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 0
console.log(cliffRichard.status); // "Single"
ご覧のように、クラシックスタイルで宣言された「クラス」のプロトタイプを操作することが可能であるため、プロトタイプ継承を使用しても実際にはメリットはありません。それは古典的な方法のサブセットです。
Web開発:プロトタイプの継承と従来の継承
http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html
クラシックとプロトタイプの継承-スタックオーバーフロー