関数をクラスとして扱う
Javascriptでは関数にクラスとしての役割を与えている。
ただし、同一のクラスをもとに生成されたインスタンスだとしても、それぞれが持つメンバが同一とは限らないなど厳密にはクラスではないことに注意。
// コンストラクタ
var Member = function(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
};
// プロトタイプにメソッドを宣言
Member.prototype.getName = function(){
return this.lastName + ' ' + this.firstName;
};
// インスタンス化
var mem = new Member('太郎', '山田');
console.log(mem.getName()); // 山田 太郎
まず、関数リテラルでベースのクラス(Member)を定義する。
これでnew Memberでインスタンスを生成できる。
メソッドの宣言はプロトタイプで行う
しかし、クラス内にメソッドをいくつも書いてしまうとインスタンス化の度にそれらのメソッドがコピーされてメモリが無駄に消費される。
そこで、なるべくベースのprototypeオブジェクトに対してメソッドを定義するようにする。
こうすることでインスタンス化の際には参照するだけになるので、メモリが無駄に消費されることはない。
プロパティの宣言はコンストラクタで行う
prototypeオブジェクトが利用されるのは「値の参照時だけ」で、値の設定は常にインスタンスに対して行われる。
var Member = function(){};
Member.prototype.sex = '男';
var mem1 = new Member();
var mem2 = new Member();
console.log(mem1.sex + ' | ' + mem2.sex); // 男 | 男
mem2.sex = '女';
console.log(mem1.sex + ' | ' + mem2.sex); // 男 | 女
インスタンスmem2のsexプロパティが書き換えられると、その時点で「インスタンスmem2自身がsexプロパティを持つ」ようになる。
プロパティをプロトタイプで定義してもこのように値が異なってしまう。
インスタンスごとに異なる値をプロトタイプで宣言する意味はないので、基本的にプロパティはコンストラクタで定義する。
thisはインスタンスを参照する
thisはインスタンスのためのものである。
次の関数triangleのような静的メソッド内では使用できない。
var Area = function(){};
Area.version = '1.0'; // 静的プロパティの定義
Area.triangle = function(base, height){ // 静的メソッドの定義
return base * height / 2;
}
console.log('Areaクラスのバージョン:' + Area.version); // 1.0
console.log('三角形の面積:' + Area.triangle(5,3)) // 7.5
静的プロパティはコンストラクタに直接追加される。
インスタンスでは使用できないのでthisも使用できない。
オブジェクトリテラルでプロトタイプを定義する
// コンストラクタ
var Member = function(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
};
// オブジェクトリテラルでプロトタイプにメソッドを宣言
Member.prototype = {
getName : function(){ return this.lastName + ' ' + this.firstName; },
toString : function(){ return this.lastName + this.firstName; }
};
こうすることで以下のようなメリットがある。
- 「Member.prototype.〜」のような記述を何回もしなくてよい
- オブジェクト名に変更があった場合に何回も書きなおさなくてよい
- メソッドがひとつのブロックにまとめられているため可読性が向上する
プロトタイプチェーン
var Animal = function(){};
Animal.prototype = {
walk: function(){console.log('トコトコ...');}
};
var Dog = function(){};
// DogオブジェクトのプロトタイプとしてAnimalオブジェクトをセット
Dog.prototype = new Animal();
Dog.prototype.bark = function(){console.log('ワンワン!');}
var d = new Dog();
d.walk(); // トコトコ...
d.bark(); // ワンワン!
DogオブジェクトのプロトタイプとしてAnimalオブジェクトをセットしている。
このときDogオブジェクトをサブクラス、Animalオブジェクトをスーパークラスという。
サブクラスではスーパークラスのメソッドも使用できる。
これはサブクラスにメソッドがなければ、スーパークラス、そのまたスーパークラスと探していくから。
これをプロトタイプチェーンという。
プロトタイプチェーンはインスタンスが生成された時点で固定化される
var Animal = function(){};
Animal.prototype = {
walk: function(){console.log('トコトコ...');}
};
var SuperAnimal = function(){};
SuperAnimal.prototype = {
walk: function(){console.log('ダダダダダ!');}
};
var Dog = function(){};
Dog.prototype = new Animal();
var d1 = new Dog(); // インスタンスを生成。スーパークラスはAnimal
d1.walk(); // トコトコ...
Dog.prototype = new SuperAnimal();
var d2 = new Dog(); // インスタンスを生成。スーパークラスはSuperAnimal
d2.walk(); // ダダダダダ!
d1.walk(); // トコトコ...
インスタンスd1が生成された時点のDogのスーパークラスはAnimal。
それが固定化されるので、d1.walkは常にトコトコ...になる。
後からDogのスーパークラスをSuperAnimalにしても変わらない。