この記事では、クラスとコンストラクターを使用しない、JavaScriptでのプログラミングの代替方法-純粋なプロトタイプOOPとECMA Script 5での実装の機能についてお話したいと思います。
OOPは、クラス指向(クラシック)とプロトタイプ指向の2つのグループに分けることができます。 古典的なアプローチは、すべてが理想的な概念によって記述されている世界に対するアリストテレスの見解を反映しています。 プロトタイプOOPは、すべておよびすべての厳密な分類と分類に依存せず、主題領域の概念を(可能な限り)物質的で直感的なものとして提示しようとする、ルートヴィヒヴィトゲンシュタインの哲学に近いものです。 プロトタイピングを支持する典型的な議論は、通常、最初に具体的な例を理解し、その後、それらを研究して一般化することによって、いくつかの抽象的な原理を強調し、その後適用する方がはるかに簡単だということです。
この分類によれば、JavaScriptは中間のどこかにあります。一方で、プロトタイプと、新しいオブジェクトを作成する手段としてのプロトタイプがあります。これは、プロトタイプ指向のアプローチの特徴ではありません。
クラス
JavaScriptにはクラスはありません。 そうは言いません。
JSのクラスとは、コンストラクター関数を意味します。インスタンスを作成する(
new
演算子を実行する)ときに呼び出される関数で、 プロトタイプへの参照(クラスのプロパティ(データ)とメソッド(関数)を含むオブジェクト)です。
ご存知のように、ECMA Script 6では
class
キーワードを導入することができます:
class Duck{ constructor(name){ this.name = name; }, quack(){ return this.name +" Duck: Quack-quack!"; } } /// class TalkingDuck extends Duck{ constructor(name){ super(name); }, quack(){ return super.quack() + " My name is " + this.name; } } /// var donald = new TalkingDuck("Donald");
しかし、本質的に、この革新は重要なもの(たとえば、
public
修飾子、
private
修飾子)をもたらすことはありません。 これは、同様のコンストラクトの構文上の砂糖に他なりません。
var Duck = function(name){ this.name = name; }; Duck.prototype.quack = function(){ return this.name +" Duck: Quack-quack!"; }; /// var TalkingDuck = function(name){ Duck.call(this, name); } TalkingDuck.prototype = Object.create(Duck.prototype); TalkingDuck.prototype.constructor = TalkingDuck; TalkingDuck.prototype.quack = function(){ return Duck.prototype.quack.call(this) + " My name is " + this.name; }; /// var donald = new TalkingDuck("Donald");
そのため、 現在のバージョンのJSには既にクラスがありますが、それらを作成するための便利な構文はありません 。
最後に、クラスとは何かを決めましょう。 定義は( ウィキペディアから )です:
クラスは、OOPの抽象データ型の一種であり、その構築方法によって特徴付けられます。 クラスと他の抽象データ型の違いの本質は、データ型を定義するときに、クラスがすべてのインスタンスのインターフェースと実装の両方を定義し、コンストラクターメソッドの呼び出しが必要なことです。この定義に従って、コンストラクター関数はクラスです:
コンストラクター関数は抽象データ型ですか? -はい。
コンストラクター関数(およびプロトタイプのプロパティ)は、インターフェイスと実装の両方を定義しますか? -はい。
インスタンスの作成時にコンストラクター呼び出しが必要ですか? -はい。
プロトタイプ
プロトタイプは次の点でクラスと異なります。
- これはすぐに使用できるオブジェクトであり、インスタンス化する必要はありません。 独自の状態を持つことができます。 プロトタイプは、クラスとインスタンスを1つのエンティティ、大まかに言ってシングルトンに結合したものであると言えます。
- オブジェクトの作成時のコンストラクター呼び出し(プロトタイプのクローン作成)はオプションです。
プロトタイプOOP自体の本質は非常に単純です。 クラシックよりもさらにシンプル。 JSの難しさは、Javaで実装されているように見せることから生じます。Javaでは、新しいオブジェクトの作成は、クラスに適用されたnew演算子を使用して行われます。 JSでは-同様に。 しかし、なぜなら JSはプロトタイプ言語のようです。 定義上 、クラスはクラスに含めるべきではありません。コンストラクター関数の概念が導入されました。 問題は、JavaScriptのコンストラクター-プロトタイプリンクの通常の説明に構文がないことです。 その結果、この厄介な省略を修正するライブラリが大量にあります。
プロトタイプ指向のアプローチには新しい演算子はなく、既存のオブジェクトを複製することで新しいオブジェクトが作成されます。
継承
そのため、プロトタイプ(委任)継承の本質は、1つのオブジェクトが別のオブジェクトを参照できることであり、これによりプロトタイプになります。 プロパティ/メソッドにアクセスするときに、オブジェクト自体で見つからない場合、検索はプロトタイプで続行され、プロトタイププロトタイプなどで続行されます。
var duck$ = {// "$" "": duck$ == Duck.prototype name: "", quack: function(){ return this.name +" Duck: Quack-quack!"; } }; var donald = { __proto__: duck$, name: "Donald" }; var daffy = { __proto__: duck$, name: "Daffy" }; console.log( donald.quack() ); // Donald Duck: Quack-quack! console.log( daffy.quack() ); // Daffy Duck: Quack-quack! console.log( duck$.isPrototypeOf(donald) ); // true
daffy
と
donald
は、1つの一般的な
quack()
メソッドを使用し、duck $プロトタイプを提供します。 プロトタイプの観点から見ると、
donald
と
daffy
は
duck$
オブジェクトのクローンであり、クラス指向のオブジェクトでは、「クラスのインスタンス」
duck$
です。
donald
(または
daffy
)
daffy
でいくつかのプロパティを直接追加/変更する場合、「クラスの相続人」
duck$
とも見なすことができます。 V8は 、プロパティが追加されるたびに非表示のクラスを作成します。
__proto__
プロパティがまだ標準化されていないことを忘れないでください。
Object.create
および
Object.getPrototypeOf
を使用して、ECMAScript 5で
Object.getPrototypeOf
プロパティを公式に操作することが
__proto__
Object.getPrototypeOf
。
var donald = Object.create(duck$, { name: {value: "Donald"} }); console.log( Object.getPrototypeOf(donald) === duck$ ); // true
初期化
クラス指向のアプローチとは異なり、プロトタイプ(クローン)に基づいてオブジェクトを作成するときのコンストラクターとその呼び出しの存在はオプションです。
オブジェクトのプロパティを初期化する方法は?
プロパティの単純で計算不可能なデフォルト値は、すぐにプロトタイプに割り当てることができます。
var proto = { name: "Unnamed" };
計算された値を使用する必要がある場合は、ECMA Script 5と共に私たちの助けになります。
遅延(遅延)初期化
遅延初期化は、最初にアクセスしたときにプロパティを初期化できる手法です。
var obj = { name: "", get lazy(){ console.log(" lazy..."); // : var value = " " + this.name; // , // , : Object.defineProperty(this, 'lazy', { value: value, writable: true, enumerable: true }); console.log(" ."); return value; }, // // , - // ( ): set lazy(value){ console.log(" lazy..."); Object.defineProperty(this, 'lazy', { value: value, writable: true, enumerable: true }); } }; console.log( obj.lazy ); // lazy... // console.log( obj.lazy );// // obj.lazy = "";// , .. console.log( obj.lazy ); //
この手法の利点は次のとおりです。
- コンストラクターをより小さなアクセサーメソッドに分割することは「自動」であり、長いコンストラクターの出現を防ぐこともできます( longメソッドを参照)。
- パフォーマンスの向上、として 未使用のプロパティは初期化されません。
比較表
試作機 | クラス(ECMAスクリプト5) | クラス(ECMAスクリプト6) |
---|---|---|
データ型の説明(「クラス」) | ||
| | |
継承 | ||
| | |
インスタンスオブジェクトの作成と初期化 | ||
| | |
パート2- パフォーマンス:__proto__を介してクラスを作成する
使用された文献のリスト:
博士 Axel Rauschmayer- 神話:JavaScriptにはクラスが必要
Antero Taivalsaari- クラスvs. プロトタイプ:いくつかの哲学的および歴史的観察[PDF]
マイク・アンダーソン- クラスベースのOOPよりもプロトタイプベースのOOPの利点