JavaScriptでクラスを定義する
JavaScriptでクラス(的なもの)を定義してみる。
プロトタイプベースのオブジェクト指向言語であるJavaScriptには、JavaやC#のようなクラスベースのオブジェクト指向言語とは違い、クラスベースのオブジェクト指向言語でいうところの意味でのクラスは存在しない為、関数オブジェクトをクラスに見立てて疑似的にクラスを定義することになる。
尚、この場でのクラスの定義は「特定のプロパティ(メソッドとメンバ変数)を持つオブジェクト」とする。
※ECMAScript6には、クラス定義が実装されているがここでは触れていない。
オブジェクトリテラルでクラスを定義
1 2 3 4 5 6 7 8 9 10 11 | // オブジェクト定義 var myObject = { name: 'myObject', method: function() { console.log('Hello! ' + this.name); } }; // メンバ変数の参照 console.log(myObject.name); // メソッドの実行 myObject.method(); |
JavaScript での最も単純なクラスの定義は、オブジェクトリテラルを使用してオブジェクトを定義する形になる。
尚、あるオブジェクトを元に新しいオブジェクトを生成する場合は、ECMAScript5で規定された「Object.create」を利用して元のオブジェクトをプロトタイプとすることで実現できる。
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 | // オブジェクト定義 var myObject = { name: 'myObject', method: function() { console.log('Hello! ' + this.name); } }; // ECMAScript5規定のObject.createが未定義の場合、Object.createを定義する if (typeof Object.create !== 'function') { Object.create = function(o) { var F = function() {}; F.prototype = o; return new F(); }; } // 新しいオブジェクトを生成 var newObject = Object.create(myObject); // 新しいオブジェクトのメンバ変数の値を変更 newObject.name = 'newObject'; // 新しいオブジェクトのメンバ変数の参照 console.log('---newObject---'); console.log(newObject.name); // 新しいオブジェクトのメソッドの実行 newObject.method(); // 元のオブジェクトのメンバ変数の参照 console.log('---myObject---'); console.log(myObject.name); // 元のオブジェクトのメソッドの実行 myObject.method(); |
コンストラクタでクラスを定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // コンストラクタ定義(関数定義) function MyClass() { // newを使用してインスタンスを生成すると暗黙的にthisオブジェクトが生成される //var this = {}; // メンバ変数定義 this.name = 'no_name'; // メソッド定義 this.method = function() { console.log('Hello! ' + this.name); }; // newを使用してインスタンスを生成すると暗黙的にthisオブジェクトがreturnされる //return this; } // インスタンス生成 var newObject = new MyClass(); // メソッド実行 console.log(newObject.name); newObject.method(); |
オブジェクトリテラルではなく、JavaやC#等のクラスベースでのオブジェクト指向のようにクラスを定義する場合は上記のようになる。
そもそもJavaScriptにクラスの概念が無い為、関数定義をnew演算子を使用して呼び出すことでコンストラクタとして働かせ、これをクラスと呼ぶ。
コンストラクタはインスタンスが生成された時に実行される関数のことであり、関数なので引数を与えることもできる。
尚、new演算子を使用しインスタンスを生成するとJavaScriptでは暗黙的に関数内で「this」という空オブジェクトの生成とそのオブジェクトのreturnの2行が追加される。
この為、thisキーワードを使用してメンバ変数を定義することができる。(this.XXX)
また、コンストラクタとしての関数内でthis以外のオブジェクトでreturnを使用するとthisが復帰されなくなってしまう為、コンストラクタとしての関数内ではreturnを使用しない。
その他、メンバ変数に無名関数として処理を代入すれば、インスタンスメソッドとして機能する。
但しこの場合、インスタンスが生成される度にインスタンスが定義され、メモリ領域を圧迫する。
この問題は、prototypeを利用することで解決できる。
※慣例として関数をコンストラクタ呼び出しで利用する場合、他の関数と区別しやすくする目的で関数名の先頭を大文字とする。
prototypeを使う
prototypeは、関数オブジェクトが定義されると自動的に生成されるメンバ変数である。
prototypeは、空オブジェクトへの参照を保持しており、インスタンス内に存在しないメンバ変数が参照された場合、prototypeを参照する。
つまり、prototypeのプロパティは関数オブジェクト自身の親への参照のようなものと捉えることができる。
1 2 3 4 5 6 7 8 9 10 11 | // 中身が空のクラスを定義 function MyClass(){ } // 定義したクラスのプロトタイプにプロパティを定義 MyClass.prototype.method = function() { console.log('Hello!'); }; // インスタンス生成 var newObject = new MyClass(); // メソッド実行 newObject.method(); |
上記の例は、中身が空のクラスを定義し、そのクラスのprototypeに対してmethodプロパティを定義することで、newObjectインスタンスからmethodプロパティを呼び出している。
prototypeは、MyClassクラスの親のようなものであり、prototypeにプロパティを定義することでMyClassのインスタンスであるnewObjectからもmethodを呼び出すことができる。
また、このようにprototypeにクラス共通のプロパティを定義をすることで、インスタンス生成の度にインスタンスメソッドが保持されることが無くなり、メモリ領域の圧迫を軽減できる。
改めてJavaScriptでのクラス定義の基本
基本的にJavaScriptでクラスを定義する際には、prototypeに汎用的な関数やクラス共通の変数を定義し、それ以外の変数はメンバ変数としてクラス内に定義することに規定すると理解しやすい。