JavaScriptにはクラスが必要ですか?

JavaScriptは、プロトタイプ指向のプログラミング言語と見なされます。 しかし、奇妙なことに、このアプローチを使用する人はほとんどいません。ほとんどの一般的なJSフレームワークは、明示的または暗黙的にクラスを操作します。

この記事では、クラスとコンストラクターを使用しない、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. これはすぐに使用できるオブジェクトであり、インスタンス化する必要はありません。 独自の状態を持つことができます。 プロトタイプは、クラスとインスタンスを1つのエンティティ、大まかに言ってシングルトンに結合したものであると言えます。
  2. オブジェクトの作成時のコンストラクター呼び出し(プロトタイプのクローン作成)はオプションです。


プロトタイプ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 ); // 
      
      





この手法の利点は次のとおりです。





比較表

試作機 クラス(ECMAスクリプト5) クラス(ECMAスクリプト6)
データ型の説明(「クラス」)
 var duck$ = { name: "Unnamed", get firstWords(){ var value = this.quack(); Object.defineProperty( this, 'firstWords', {value: value} ); return value; }, quack: function(){ return this.name +" Duck: Quack-quack!"; } };
      
      



 var Duck = function(name){ this.name = name||"Unnamed"; this.firstWords = this.quack(); }; Duck.prototype.quack = function(){ return this.name +" Duck: Quack-quack!"; };
      
      



 class Duck{ constructor(name="Unnamed"){ this.name = name; this.firstWords = this.quack(); }, quack(){ return this.name +" Duck: Quack-quack!"; } }
      
      



継承
 var talkingDuck$ = Object.create(duck$, { quack: {value:function(){ return duck$.quack.call(this) + " My name is " + this.name; }} });
      
      



 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; };
      
      



 class TalkingDuck extends Duck{ constructor(name){ super(name); }, quack(){ return super.quack() + " My name is " + this.name; } }
      
      



インスタンスオブジェクトの作成と初期化
 var donald = Object.create(talkingDuck$); donald.name = "Donald";
      
      



 var donald = new TalkingDuck("Donald");
      
      



 var donald = new TalkingDuck("Donald");
      
      







パート2- パフォーマンス:__proto__を介してクラスを作成する






使用された文献のリスト:

博士 Axel Rauschmayer- 神話:JavaScriptにはクラスが必要

Antero Taivalsaari- クラスvs. プロトタイプ:いくつかの哲学的および歴史的観察[PDF]

マイク・アンダーソン- クラスベースのOOPよりもプロトタイプベースのOOPの利点



All Articles