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つのプロパティを持つ点である。 widthheight はオブジェクトごとに値が違うが、area は共通の関数を参照するため、オブジェクトごとに持つ必要はない。

この問題を解決するのがプロトタイプである。 実はすべての javascript オブジェクトには、プロトタイプオブジェクトというオブジェクトへの参照が含まれる。(prototype プロパティ)

new 演算子を使用してオブジェクトを生成すると、コンストラクタ関数の prototype プロパティの値が、生成されたオブジェクトのプロトタイプになる。 生成されたオブジェクトは自身のプロトタイプのプロパティを参照することができる。

例えば、上記の例であればコンストラクRectangleprototype にメソッド 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 とかで変換して・・・ってのが普通だし、そこらのテクニックはそんなに意識しなくてよいのかなと思うので割愛。