O'REILLY JavaScript 第5版を飛ばし読む(9章 クラスとコンストラクタとタイプ)
仕事で JavaScript 結構書くので、基礎的なところは抑えときたいと思って、O'REILLY JavaScript 第5版を飛ばし読みしています。まずは JavaScript でのクラスの実現方法から。
JavaScript におけるコンストラクタ、メソッド
JavaScriptのオブジェクトは、new
演算子またはオブジェクトリテラルで {}
を使用することで生成できる。
new
演算子と一緒に使う関数をコンストラクタと呼ぶ。
また、オブジェクトのプロパティとして呼び出される関数をメソッドと呼ぶ。
この形式で関数を呼び出すと、関数を呼び出したときに使われたオブジェクトが this
キーワードの値になる。
// コンストラクタ funtion Rectangle(w, h) { this.width = w; this.height = h; // メソッド this.area = fucntion() { return this.width * this.height; } } // オブジェクトの生成 var r = new Rectangle(2, 4); //または rect = {width: 2, height: 4} // 面積の計算 var a = rect.area;
プロトタイプと継承
上記の書き方には問題がある。それは生成されたすべての Rectangle
オブジェクトが、3つのプロパティを持つ点である。
width
と height
はオブジェクトごとに値が違うが、area
は共通の関数を参照するため、オブジェクトごとに持つ必要はない。
この問題を解決するのがプロトタイプである。
実はすべての javascript オブジェクトには、プロトタイプオブジェクトというオブジェクトへの参照が含まれる。(prototype
プロパティ)
new
演算子を使用してオブジェクトを生成すると、コンストラクタ関数の prototype
プロパティの値が、生成されたオブジェクトのプロトタイプになる。
生成されたオブジェクトは自身のプロトタイプのプロパティを参照することができる。
例えば、上記の例であればコンストラクタ Rectangle
の prototype
にメソッド area
を設定すれば、
すべての Rectangle
オブジェクトから共通の area
を参照できるようになる。これは Java でいう継承にあたる。
// コンストラクタ funtion Rectangle(w, h) { this.width = w; this.height = h; } // プロトタイプ Rectangle.prototype.area = fucntion() { return this.width * this.height; }
プロトタイプを適切に使用することで、使用するメモリを大幅に節約することができる。 また、あるオブジェクトを生成した後に、そのオブジェクトのプロトタイプにプロパティを追加しても、その値を継承することができる。
継承プロパティへのアクセス
オブジェクト o
のプロパティ p
を読み出す時は、まずオブジェクト o
にプロパティ p
があるかをチェックし、
なければオブジェクト o
のプロトタイプにプロパティ p
があるかをチェックする。
一方で、オブジェクト o
にプロパティ p
を書き込む時は、オブジェクト o
がプロパティ p
を持たない場合、
自動でオブジェクト o
に新しいプロパティ p
が追加される。
これは、仮にプロトタイプのプロパティ p
を書き換えると、同じプロトタイプを継承しているすべてのオブジェクトに影響が出るためである。
JavaScriptにおけるクラス
JavaScript にはクラスという正式の概念はない。ただし、上述したようにプロトタイプを用いて継承を実現しており、 Java などクラスベースのオブジェクト指向言語の長所をかなり実現している。
java などの言語と大きく異なる点は、プロパティやその型が予め決められておらず、動的に追加可能である点である。
※ これらの点について、最近のフロントエンド開発では ES6 以降の新しい構文や flow などのツールを用いることで改善が図られている。詳細はまたどこかで。。。
JavaScript における継承の例
以下に前述した Rectangle
クラスを継承したサブクラスである PositionedRectangle
クラスの例を示す。
サブクラスを作成する場合は、そのプロトタイプオブジェクトにスーパークラスのインスタンスを指定すればよい。
// Rectangle クラスに位置情報を追加した PositionedRectangle クラスのコンストラクタ funtion PositionedRectangle(x, y, w, h) { // call メソッドでスーパークラスのコンストラクタを呼び出す Rectangle.call(this, w, h); this.x = x; this.y = y; } // スーパークラスのコンストラクタを指定することで、サブクラスからスーパークラスのプロパティが参照可能になる PositionedRectangle.prototype = new Rectangle(); // スーパークラスの area メソッドのみを継承し、 width や heightの値自体は継承したくない場合 // プロトタイプから削除する delete PositionedRectangle.prototype.width; delete PositionedRectangle.prototype.height; // コンストラクタプロパティは、自身を参照するよう書き換える PositionedRectangle.prototype.constructor = PositionedRectangle;
上記のようにサブクラスを生成せずに、 別のクラスのプロトタイプをあるクラスのプロトタイプへコピーして別のクラスのメソッドを継承する事もできる。(詳細な説明は割愛)
感想
プロトタイプを用いたクラスと継承の仕組みが理解できたのは良かった。 種々の JavaScript ライブラリ使ってても、デベロッパーツールとか使ってデバッグするときは生の JavaScript 読むことになるし、そういうときに上記を知っていれば捗る気がする。
本には JavaScript におけるオブジェクトクラスの判定法とかいろいろ書いてあるけど、最近は ES6 使って Babel とかで変換して・・・ってのが普通だし、そこらのテクニックはそんなに意識しなくてよいのかなと思うので割愛。