クラスの構築
コンストラクタ
JavaScriptではクラス定義はなく、コンストラクタを関数で定義する。
this
はこの関数が含まれるオブジェクト(のインスタンス)を指すnew
によって新しいオブジェクトの無名インスタンスが生成される- 代入演算子(=)によって、変数が上記のインスタンスを参照する
- その結果、
this
は定義されたインスタンスを指す
コンストラクタで定義されていないプロパティでも、インスタンスごとに後から追加可能だが、これは可読性やクラスとしての一貫性を損なう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function MyClass( name ) { this.name = name; } var john = new MyClass( "John" ); var louis = new MyClass( "Louis" ); // インスタンスごとにプロパティが保持される console.log( john.name ); console.log( louis.name ); // 未定義のプロパティはundefined console.log( john.age ); console.log( louis.age ); // 後からinstanceごとにプロパティを追加可能 instance1.age = 30; console.log( john.age );// 30 console.log( louis.age );// undefined |
インスタンスの識別
同じクラスのインスタンスへの参照が同一か異なるかを比較することができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function MyClass() { } me = new MyClass(); copy = me; other = new MyClass(); console.log( ( me == me ) ); // true console.log( ( me == copy ) ); // true console.log( ( me == other ) ); // false console.log( ( copy == other ) ); // false console.log( ( me != me ) ); // false console.log( ( me != copy ) ); // false console.log( ( me != other ) ); // true console.log( ( copy != other ) ); // true |
この比較はthisを用いても可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function MyClass() { } MyClass.prototype.isSameAs = function( other ) { return ( this == other ) ? true : false; } me = new MyClass(); copy = me; another = new MyClass(); console.log( me.isSameAs( me ) ); // true console.log( me.isSameAs( copy ) ); // true console.log( me.isSameAs( another ) ); // false |
メソッド
非効率的な方法
コンストラクタの中で、プロパティと同じようにthis
.[関数名]と無名関数定義によってプロパティを定義可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function MyClass( name ) { this.name = name; this.age; this.setAge = function( age ) { this.age = age; } } var john = new MyClass( "John" ); john.setAge( 30 ); console.log( john.age ); |
ただし、thisで指定された対象はインスタンスごとに生成・保持されるため、上記コードではインスタンスの数分のメソッドが生成されてしまう。
prototypeによる方法
JavaScriptにおける全てのオブジェクトはObject
に由来する。すべてのオブジェクトはObject.prototype
からメソッドとプロパティを継承しているが、それらは上書きすることが可能。
インスタンスでメソッドを実行しようとした時にそのメソッドが存在しない場合、そのクラスのprototypeにメソッドを探しに行く。 新たに定義したクラスのprototypeプロパティに関数を定義しておくことで、全てのインスタンスが共通のprototype下のメソッドを利用することになる。
prototypeにメソッドを定義する方法の一つは、メソッド単位でクラスのprototypeに登録する方法と、オブジェクトリテラルでまとめて定義する方法がある。
メソッド単位で定義する方法
メソッドごとにクラスのprotottypeに一つずつ記述していく方法。
1 2 3 4 5 6 7 8 9 10 11 |
function MyClass() { this.name = ""; } MyClass.prototype.setName = function( name ) { this.name = name; } MyClass.prototype.getName = function() { return( this.name ); } |
オブジェクトリテラルでまとめて定義する方法
クラスの初期設定時に連想配列で定義する。
この方法は、新規にメソッドを定義する場合に一回のみ使用するもので、後から同じ方法でメソッドを追加しようとしした場合、最初の定義内容がすべてオーバーライドされる。
ただ、メソッド定義を敢えてダイナミックに変更するのでなければ、この方法はメソッドの定義のあり方として明快ともいえる。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function MyClass() { this.name = ""; } MyClass.prototype = { setName: function( name ) { this.name = name; }, // ここのカンマに注意 getName: function() { return( this.name ); } } |
メソッド定義の注意点
クラスの生成はコンクトラクタの前後を問わないが、メソッドを実行する段階では、prototypeへのメソッドの登録が実行済みでなければならない(記述位置の前後関係ではなく、関数呼び出しも含めた実体上の実行順序)。
C++やJavaなどコンパイラが全体の宣言・定義をパースする場合は問題ないが、インタプリタ系のJavaScriptは、定義=宣言と実行の順序については厳しい。
継承
子クラスのprototypeに親クラスを登録することで継承でき、親クラスのプロパティ、メソッドも利用可能となる。
1 |
ChildClass.prototype = new ParentClass(); |
これは厳密な意味でのクラスの継承というよりは、そのような挙動をするためのprototypeチェーンに組み込んだというところ。
注意点
子クラスのメソッドの定義は必ず親クラスの継承の後で行うこと。継承の際にprototypeの内容が上書きされてしまうため、継承の前にメソッド定義すると子クラスのメソッドが参照されなくなってしまう(下記の例では、”instance.setAge is not a function”とエラーになる)。
子クラスのメソッド定義でオブジェクトリテラルを使ってはいけない。継承の後にこれを行うと、親クラスのメソッドがすべて上書きされてしまう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
// 親クラスとメソッドの定義 function ParentClass() { this.name = ""; } ParentClass.prototype = { setName: function( name ) { this.name = name; } } // 子クラスの定義 function ChildClass() { this.age = 0; } // クラスの継承 ChildClass.prototype = new ParentClass(); // 【重要】子クラスのメソッド定義は必ず継承の後で // 【重要】子クラスのメソッド定義は必ずprototypeへの追加で ChildClass.prototype.setAge = function( age ) { this.age = age; } /* // 【重要】子クラスのメソッド定義でオブジェクトリテラルは使えない ChildClass.prototype = { setAge: function( age ) { this.age = age; } } */ // テスト var instance = new ChildClass(); instance.setName( "John" ); instance.setAge( 30 ); console.log( instance.name ); console.log( instance.age ); |