[JavaScript] クラスの作成
// 名前空間の定義 if (!window.comexample) comexample = {}; // クラスの定義 (function () { // コンストラクタ function Sample() { // プロパティ(メンバ変数) this.prop = 1; // コンストラクタからメソッドの呼び出し Sample.prototype.doMethod.call(this); } // 名前空間用のオブジェクトに代入 comexample.Sample = Sample; // メソッド Sample.prototype.doMethod = function () { ++this.prop; // メソッドから別のメソッドの呼び出し this.getProp(); } // アクセサ(getter/ setter) Sample.prototype.getProp = function () { return this.prop; } Sample.prototype.setProp = function (value) { this.prop = value; } // 静的プライベートメンバ var privateStaticVar = 2; // 静的プライベートメソッド function doPrivateStaticMethod() { ++privateStaticVar; } // 静的パブリックメンバ(クラス変数) Sample.staticVar = 3; // 静的パブリックメソッド Sample.doStaticMethod = function () { doPrivateStaticMethod(); ++Sample.staticVar; } })(); // 即実行 new comexample.Sample();
名前空間
JavaScriptには名前空間はないので、Objectをそのかわりに使う。
あくまで実体はObjectのインスタンスでしかないので、他の言語のように「com.example.Sample」などとしてしまうと、どこかで誰かが「com = 1;」のような処理をした場合、com以下のオブジェクトにアクセスできなくなってしまう。
なるべく長めの名前したり、YUIの「YAHOO.xxx」ように大文字にしたりして、他の識別子と重複しないように気をつける必要がある。
なお、グローバル領域の変数をいきなり「if (!comexample)」とすると、「comexampleが定義されていない」とのエラーが出てしまう場合があるので、グローバルオブジェクトのプロパティとみなして「window.comexample」と書いておく。
クラスの定義
後述の静的プライベートメンバをつくるために、即時関数(即時実行する無名関数)の内部でクラスを定義したほうがいい。
コンストラクタ
JavaScriptのクラスは、実際には関数でしかない。そのprototypeを利用して「設計」を定義し、クラスとなる関数自体がコンストラクタとなり、それは「Sample.prototype.constructor()」と同一。
プロパティ
プロパティ(メンバ変数、インスタンス変数)は、thisに直接つくる。
コンストラクタをnewで呼び出した場合、その内部のthisはインスタンス自身を示す。
なぜprototypeにつくらないのか。
理由1:「Sample.prototype.prop = 5;」のようにprototypeを直接書き換えると、すべてのインスタンスの該当メンバが書き換えられるという言語仕様のため(くわしくはこちら)。
逆にそれを利用したいなら、あえてprototypeに記述してもいい。
理由2:関数と異なり、prototypeに記述してもメモリの節約などにはほとんどならないため(後述)。
コンストラクタからメソッドの呼び出し
コンストラクタからメソッドを呼び出す場合は、call/ apply()関数を利用し、thisを明確にして実行する。
クラスの継承をするときのため(くわしくはこちら)。
メソッド
prototypeに新しいプロパティをつくり、そこに関数を代入する。
prototypeは「設計」を保持するものなので、まさにクラスの定義に利用できる。
// (1) function createSampleInstance() { var instance = {} instance.doMethod = function () { //... } return instance; } // (2) function Sample() { //... } Sample.prototype.doMethod = function () { //... } new Sample;
この例では、(1)は設計と実装がひとつになってしまっている。つまり、インスタンス(オブジェクト)をひとつつくるたびに、設計もひとつずつつくっている=createSampleInstance()を実行するたびに再定義している形のため、メモリ使用の面などで無駄が多い。
(2)はオブジェクト指向的に設計=prototypeと実装が分かれているため、どんなにインスタンスを生成しても設計=prototypeはひとつなので、無駄がない。
JavaScriptはオブジェクト指向ではないという人もいるが、これが「prototypeベースのオブジェクト指向」と呼ばれるゆえんである。
ただ、プロパティの場合と同じく、prototypeの関数(メソッド)を変更すると、すべてのインスタンスがその影響を受けるため、メモリ使用効率などを考慮しなくていいなら、あえて(1)のやり方をとるケースもある。
アクセサ
prototypeに入れようとインスタンスに直接定義しようと、すべてのメンバはパブリックになる。
外部から「new comexample.Sample().prop」とアクセスできるのだが、念のためアクセサ用のメソッドを通しておいたほうがいいだろう。
静的プライベートメンバ
実際にはクラスの定義に使っている即時関数のローカル変数でしかないが、クロージャとして機能するので、外部に非公開の静的プライベートメンバとして利用できる。
静的プライベートメソッド
上と同じく、実際には即時関数の関数内関数。
静的パブリックメンバ・メソッド
prototypeではなく、クラス(コンストラクタ、関数)に直接設定。
インスタンスにかかわりなくどこからでも共通して呼び出せるので、静的なパブリックメンバ・メソッドとして使える。