JavaScriptでは、本来のオブジェクト指向言語とは少し異なり、
正確なクラスの定義は出来ません。
しかし、JavaScriptでは強力な関数定義方法が用意されていますので、関数をクラスとして
実装する事で対応するのが一般的です。
今回の記事では、そんなJavaScript流のクラス定義方法をソースコード付きで解説します。
- クラス定義とインスタンス生成時のコンストラクタについて
- クラス内のメンバ変数を定義しよう
- インスタンスメソッドを定義しよう
- クラス変数とクラスメソッドについて
- クラスを継承してみよう
- インスタンスメソッドのオーバーライド
クラス定義とインスタンス生成時のコンストラクタについて
JavaScriptでは、クラスの定義は関数の定義と同様に『function』を用いて定義します。
関数名がクラス名となり、関数内の処理がクラスのコンストラクタとなります。
定義したクラスをインスタンス化させるには『new』を使用してインスタンスを生成させます。
// クラス名『MyClass』を定義 function MyClass(){ // コンストラクタ alert( 'コンストラクタが呼ばれました。' ); } // MyClassを生成 var myClassInstance = new MyClass()
勿論、通常の関数の様に引数を受け取る事も可能で、受け取った値はコンストラクタ内で使用可能です。
// クラス名『MyClass』を定義 function MyClass( name ){ // コンストラクタ // コンストラクタとして渡された値をアラートで表示してみる alert( name ); } // MyClassを生成時にコンストラクタへ値を渡す var myClassInstance = new MyClass( 'hamada' )
クラス内のメンバ変数を定義しよう
クラスに対するメンバ変数は、関数自体を参照する『this』に対して設定する事で関数自体に値を保持させる事が出来ます。
this.<メンバ変数名> = <格納する値>;
保持したメンバ変数の参照も、インスタンスから直接メンバ変数を参照して取得します。
var val = Instance.<メンバ変数名>;
それでは、いくつかのインスタンスを生成して、それぞれのメンバ変数へ値を保持させて見ましょう。
メンバ変数として機能させる為には、クラス定義は一つだけでもインスタンス毎に値を保持出来なければなりません。
下記の様に、クラス内にメンバ変数を定義し、コンストラクタ内でメンバ変数へ値を格納。
複数のインスタンス生成時に、それぞれ異なる値を指定して、
それぞれのメンバ変数の値を確認してみましょう。
// クラス名『MyClass』を定義 function MyClass( name ){ // コンストラクタ // メンバ変数へ値を格納 this.name = name; } // コンストラクタへ値を渡しながらMyClassをインスタンス化 var myClassInstance1 = new MyClass( 'hamada' ); var myClassInstance2 = new MyClass( 'yuu' ); // myClassのメンバ変数『name』をアラートで表示してみる alert( myClassInstance1.name ); alert( myClassInstance2.name );
それぞれのインスタンス内に、別々の値が格納されている事が確認出来たかと思います。
定義する順番や、メンバ変数名を変えてみたりと様々なテストを行ってみて下さい。
勿論、メンバ変数は一つだけでなく複数定義する事が可能です。
インスタンスメソッドを定義しよう
クラスにはメンバ変数以外にも、クラス毎の持つ関数『インスタンスメソッド』も定義しておくと便利です。
基本的なメソッドとして、クラス内のメンバ変数に対して値をセットする『セッターメソッド』と、メンバ変数の値を取得する『ゲッターメソッド』が定義されていた場合、
きっと下記の様な使い方を想定するかと思います。
// 名前をセットする myClassInstance1.setName( 'HAMADA' ); // 名前を取得する var name = myClassInstance1.getName();
JavaScriptでは、無名関数という関数名を定義しない関数も扱う事が出来ますので、
クラス内のメンバ変数に対して無名関数を設定する事で実現可能です。
// クラス名『MyClass』を定義 function MyClass( name ){ // コンストラクタ // メンバ変数へ値を格納 this.name = name; // インスタンスメソッドの定義 this.getName = function(){ // メンバ変数の値を返却 return( this.name ); } this.setName = function( name ){ // メンバ変数へ値を保持 this.name = name; } } // コンストラクタへ値を渡しながらmyClassをインスタンス化 var myClassInstance1 = new MyClass( 'hamada' ); // 名前をセットする myClassInstance1.setName( 'HAMADA' ); // 名前を取得する var name = myClassInstance1.getName(); // 取得した名前の確認 alert( name );
しかし、『this』の値はインスタンス毎に保持するものですので、上記のコードでは
クラスをインスタンス化する度にメソッドを作成して内部に保持するといった動作になってしまうため、とても非効率で、とてもメモリの無駄遣いです。
そこで、これらの無駄を解決する為にクラス定義自体への参照を持つ『prototype』に対して無名関数を設定してあげましょう。
JavaScriptでは、関数オブジェクト内に『prototype』というメンバ変数を保持しており、
関数オブジェクトが定義された時点で自動的に作成され、関数定義に対しての直接的な参照を保持しています。
JavaScriptでは、指定のインスタンス内に存在しないメンバ変数が参照された場合、関数定義自体の『prototype』へ値を探しに行きます。
ですので、『prototype』へメソッドを定義する事によって、インスタンス化の度にコンストラクタ内で無名関数を作成してメンバ変数へ保持する必要が無くなり、何度インスタンス化しても、メソッドを使用する際には『prototype』へ設定されたメソッドを使用する事になり、とても効率的です。
『prototype』を使用した具体的な実装例を下記に示します。
// クラス名『MyClass』を定義 function MyClass( name ){ // コンストラクタ // メンバ変数へ値を格納 this.name = name; } // インスタンスメソッドの定義 MyClass.prototype = { // メンバ変数の値を取得する getName: function(){ // メンバ変数の値を返却 return( this.name ); }, // メンバ変数の値を設定する setName: function( name ){ // メンバ変数へ値を保持 this.name = name; } } // コンストラクタへ値を渡しながらmyClassをインスタンス化 var myClassInstance1 = new MyClass( 'hamada' ); // 名前をセットする myClassInstance1.setName( 'HAMADA' ); // 名前を取得する var name = myClassInstance1.getName(); // 取得した名前の確認 alert( name );
『prototype』に対して連想配列にて纏めてインスタンスメソッドを定義しています。
添え字にはインスタンスメソッド名、そして値には無名関数としてインスタンスメソッドの処理を設定します。
このインスタンスメソッド内の『this』は、『prototype』を実際に参照したインスタンスを指します。
ですので、それぞれのインスタンスが持つ異なる値を扱う事が可能です。
また、上記の様に配列として定義しなくても、直接『prototype』へ設定する事も出来ます。
// メンバ変数の値を取得する MyClass.prototype.getName = function(){ // メンバ変数の値を返却 return( this.name ); } // メンバ変数の値を設定する MyClass.prototype.setName = function( name ){ // メンバ変数へ値を保持 this.name = name; }
どちらの方法にて記述するかは好みですので、可読性と保守性、実装効率等を
考慮して使用方法を検討して下さい。
クラス変数とクラスメソッドについて
クラス変数とクラスメソッドの定義方法はとても簡単です。
JavaScriptでは、関数定義時に関数オブジェクトが作成されますが、
その関数オブジェクトに対して直接設定します。
// クラス名『MyClass』を定義 function MyClass(){ // コンストラクタ } // クラス変数を定義 MyClass.country = 'japan'; // クラスメソッドを定義 MyClass.gatCountry = function(){ return this.country; } // クラス変数を確認 alert( MyClass.country ); // クラスメソッドの起動 alert( MyClass.gatCountry() );
クラスメソッド内の『this』は関数オブジェクト自体を指しますので、
クラスメンバとして定義された『country』の値を参照する事が出来ます。
クラス変数・クラスメソッドはとても簡単に利用出来ますが、
定義や使用箇所が乱雑になりやすいので、自分なりのルールを明確にして
秩序を守った利用を心がけましょう。
クラスを継承してみよう
クラスの継承は、一見複雑そうに見えても、実はとても単純です。
インスタンスメソッドの項目で少し解説しましたが、JavaScriptの関数オブジェクトが持つ『prototype』を使用します。
『prototype』は、指定の変数やメソッドが無ければ『prototype』に設定された値を参照して再調査するとお話しました。
継承関係も、子クラスに定義されていない事を要求された場合は、親クラスへ要求を出します。それはすなわち、子クラスの『prototype』に対して親クラスのインスタンスを設定しておけば、継承関係を実現できそうです。
まずは下記コードを参照して下さい。
// 親クラスを定義 function ParentClass(){ // コンストラクタ // メンバ変数へ値を保持 this.country = 'japan'; } // 親クラスのインスタンスメソッドを定義 ParentClass.prototype.getName = function(){ // メンバ変数の値を返却 return( this.name ); } // 子クラスの定義 function MyClass( name ){ // コンストラクタ // メンバ変数へ値を保持 this.name = name; } // 親クラスを継承する MyClass.prototype = new ParentClass(); // 子クラスのインスタンスを生成する var myClassInstance = new MyClass( 'hamada' ); // 子クラスのインスタンスから親クラスのメソッドを呼び出し // 子クラスが持つメンバ変数の値を取得する alert( myClassInstance.getName() ); // 子クラスのインスタンスから、親クラスのメンバ変数を取得する alert( myClassInstance.country );
親クラスとして『ParentClass』、子クラスとして『MyClass』を定義しています。
親クラスには、インスタンスメソッドとして『getName()』を定義しており、返却値として
『this.name』を指定しています。
しかし、親クラスには『name』というメンバ変数は無いので、そのまま『getName()』を起動しても値を取得する事は出来ません。
その反面、子クラスにはメンバ変数として『name』を持っています。
これらを上手く連動させる為に、まずは子クラスへ親クラスを継承させましょう。
// 親クラスを継承する MyClass.prototype = new ParentClass();
子クラスの『prototype』へ親クラスのインスタンスを指定します。
それでは実験してみましょう。
// 子クラスのインスタンスを生成する var myClassInstance = new MyClass( 'hamada' ); // 子クラスのインスタンスから親クラスのメソッドを呼び出し // 子クラスが持つメンバ変数の値を取得する alert( myClassInstance.getName() );
子クラスをインスタンス化し、子クラスに定義されていないインスタンスメソッド『getName()』を実行します。通常ではエラーとなりますが、今回は継承していますので
親クラスの『getName()』が子クラス上で起動され、『getName()』内に実装されている返却値の指定『this.name』の『this』が子クラスを指す様になり、結果として子クラスの『name』の値が取得出来ます。
続いて、子クラスに存在しないメンバ変数に対してアクセスしてみましょう。
// 子クラスのインスタンスから、親クラスのメンバ変数を取得する alert( myClassInstance.country );
こちらも、子クラスには存在しない値を要求していますので、子クラスの『prototype』へ
『country』を探しに行き、結果として親クラスの『country』を取得します。
インスタンスメソッドのオーバーライド
クラスを継承して、子クラスから親クラスのインスタンスメソッドを起動する事が出来る用になりました。ですが、子クラスでは親クラスのインスタンスメソッドとは異なる動作を行いたい事があります。
そんな時に行うのが、メソッドのオーバーライド(上書き)です。
親クラスのインスタンスメソッドを継承しているにも関わらず、メソッドの振る舞いを
変更したい時は、同じメソッド名で子クラスにもインスタンスメソッドを定義します。
// 親クラスを定義 function ParentClass(){ // コンストラクタ } // 親クラスのインスタンスメソッドを定義 ParentClass.prototype.getName = function(){ // メンバ変数の値を返却 return( this.name ); } // 子クラスの定義 function MyClass(){ // コンストラクタ // メンバ変数へ値を保持 this.name = 'hamada'; this.country = 'japan'; } // 子クラスのインスタンスメソッドを定義 ParentClass.prototype.getName = function(){ // メンバ変数の値を返却 return( this.country ); } // 親クラスを継承する MyClass.prototype = new ParentClass(); // 子クラスのインスタンスを生成する var myClassInstance = new MyClass(); // インスタンスメソッドを起動する alert( myClassInstance.getName() );
上記の例ですと、親クラスのインスタンスメソッドでは『name』を返却しようとします。
しかし、子クラスでは『name』ではなく『country』の値を返却したいので、子クラスでも
親クラスのインスタンスメソッドと同じ名前で定義しました。
『prototype』に指定された値を利用するのは、要求を受けた関数オブジェクト内に対象が存在しない時だけです。
今回の場合では子クラスに『getName()』が定義されていますので、親クラスを継承していたとしても、親クラスの『getName()』は使用されずに、子クラスの『getName()』が起動されます。
この様に、インスタンスメソッドのオーバーライドは可能となります。
JavaScriptはとても柔軟で、様々な実装方法が可能です。
そんな柔軟さはメリットでもありますが、人によって記述方法が異なったり、
適当に実装を開始すると、途端にスパゲッティコードになる可能性も秘めています。
処理内容を把握した後は、自分なりに可読性や保守性を考えて
処理を分割したり、纏める工夫をしたりしてスマートな方法を模索して下さい。
その為に、今回解説したJavaScriptにおけるクラス定義方法は役に立つかと思います。
是非、ご自身のプロジェクトに取り込んで活用して頂ければ幸いです。
また、今回解説したクラス定義方法の応用として、
少し特殊なことをしたい場合は下記を参考にしてください。
[JavaScript]子クラスから親クラスの同名メソッドを呼び出す方法
[JavaScript]子クラスから親クラスを引数付きで初期化する方法
とても解りやすい記事をありがとうございます。
返信削除処で、一番最後の画像の22行目は「MyClass.prototype.getName = function(){」のタイポですかね。