- 関数には prototype というプロパティが必ず存在する。
- ここにはオブジェクト(の参照値)がセットされている。
- このオブジェクトはその関数がコンストラクタとして使用された場合、生成されるオブジェクト(インスタンス)のプロトタイプオブジェクトとなる。
- オブジェクトには __proto__ というプロパティが必ず存在する。
- ここにはそのオブジェクトのプロトタイプオブジェクト(の参照値)がセットされている。
- new 演算子によってオブジェクト(インスタンス)が生成されると、インスタンスの __proto__ プロパティにコンストラクタ関数の prototype プロパティにセットされている参照値がコピーされる。
プロトタイプチェーン
オブジェクトのプロパティにアクセスする際、そのオブジェクトに目的のプロパティが無かったら、__proto__ プロパティの参照先のオブジェクト(プロトタイプオブジェクト)に飛んでそちらを探す。それでも無かったら、さらにそのオブジェクトの __proto__ プロパティの参照先に飛ぶ。そうやって __proto__.__proto__._proto__ と、どんどん辿って探していく。このオブジェクトの連鎖の仕組みがプロトタイプチェーン。
クラスの作成
基本
// Constructor
function MyClass() {
// Private property
var prop1 = 'priv_prop';
// Public property
this.prop2 = 'public_prop_1';
// Private method
function method1(){ }
// Privileged(特権) method
this.method2 = function(){ };
}
// Prototype
MyClass.prototype = {
// Public property (read only)
prop3 : 'public_prop_2',
// Public method
method3 : function(){ },
};
// Static
MyClass.prop4 = 'static_prop';
MyClass.method4 = function(){ };
- プライベートメンバはプライベートメソッドか特権メソッドからしかアクセス出来ない。
- つまりプロトタイプに定義されたパブリックメソッドからは無理ということ。
- 特権メソッドはプライベートメンバを扱えるが、インスタンス毎に作成されるのでメモリ効率が悪い。
- プロトタイプに定義されたプロパティはインスタンスから見ると読み取り専用の初期値のような存在。インスタンスから変更しようとするとインスタンスに新たにプロパティが作られる事に注意。
- スタティックメンバ(クラスプロパティ・クラスメソッド)はインスタンスとはまったく無関係。インスタンスからはアクセス出来ない。
ケアレスミスでグローバル汚染が起きるのを防ぐ
コンストラクタ関数を呼び出す時、ケアレスミスで new 演算子を忘れると this = window(global) となり、グローバル汚染が起こる。これを防ぐ。
// Constructor
function MyClass() {
this.initInstance.apply(this, arguments);
}
// Prototype
MyClass.prototype = {
initInstance : function() {
// Private property
var prop1 = 'priv_prop';
// Public property
this.prop2 = 'public_prop_1';
// Private method
function method1(){ }
// Privileged(特権) method
this.method2 = function(){ };
},
// Public property (read only)
prop3 : 'public_prop_2',
// Public method
method3 : function(){ },
};
// Static
MyClass.prop4 = 'static_prop';
MyClass.method4 = function(){ };
これなら new 演算子を忘れて呼ばれても、window.initInstance が存在しないかぎり例外エラーが発生するのですぐ分かる。
クラスの継承
基本
// 親クラス
function ParentClass(arg) {
this.p_prop = arg;
}
ParentClass.prototype = {
p_method : function() {},
};
// 子クラス
function ChildClass(arg) {
this.c_prop = arg;
}
ChildClass.prototype = new ParentClass();
ChildClass.prototype.c_method = function(){ };
親クラスのメソッドがプロトタイプオブジェクトとして共有継承されるのは良いが、プロパティまで共有で継承されてしまい、純粋な継承とは言い難い。
プロパティの継承を工夫
// 子クラス
function ChildClass(arg, ...args) {
ParentClass.apply(this, args); // あるいは .call() を使ってもいい
this.c_prop = arg;
}
これでプロパティの継承は出来たが、プロトタイプオブジェクトにも同様のプロパティが存在して2重になっている無駄がある。
プロトタイプオブジェクトの設定を工夫
var F = function(){};
F.prototype = ParentClass.prototype;
ChildClass.prototype = new F();
これで2重なのが解消
constructor プロパティの不具合を修正
例えば、
function Const_A() {
}
function Const_B() {
}
Const_B.prototype = ( Obj_A = ) new Const_A(); // 理解しやすいように Obj_A と一拍置く
var Obj_B = new Const_B();
この場合の Obj_B.constructor は本当なら Const_B を指して欲しいが、実際は Const_A を指してしまう。
なぜなら
Obj_B.constructor
⇒ Obj_B.__proto__.constructor // 一度目のプロトタイプチェーン
⇒ Const_B.prototype.constructor
⇒ Obj_A.constructor
⇒ Obj_A.__proto__.constructor // 二度目のプロトタイプチェーン
⇒ Const_A.prototype.constructor
⇒ Const_A
となるから。
なので、これを解決するためには、二度目のプロトタイプチェーンが発生しないように、
Const_B.prototype(Obj_A) に constructor プロパティを作成する。
Const_B.prototype.constructor = Const_B;
上の二つをまとめて読みやすく再利用しやすいようにする
Function クラスに独自メソッド inherit を設定すると使いやすい
Function.prototype.inherit = function(baseClass) {
var F = function(){};
F.prototype = baseClass.prototype;
this.prototype = new F();
this.prototype.constructor = this;
}
こう設定しとけば、クラス継承をする時
ChildClass.inherit(ParentClass);
とするだけでプロトタイプオブジェクトの設定が出来るので楽。
ついでにクラス継承の情報をクラスプロパティとして保持しとくのも良いかも。
inherit メソッドの最後に
this.baseClass = baseClass;
みたいな感じで。
まとめ
Function.prototype.inherit = function(baseClass) {
var F = function(){};
F.prototype = baseClass.prototype;
this.prototype = new F();
this.prototype.constructor = this;
this.baseClass = baseClass;
}
// 親クラス
function ParentClass(arg) {
this.p_prop = arg;
}
ParentClass.prototype = {
p_method : function() {},
};
// 子クラス
function ChildClass(arg, ...args) {
ParentClass.apply(this, args);
this.c_prop = arg;
}
ChildClass.inherit(ParentClass);
ChildClass.prototype.c_method = function(){ };
最終更新:2014年11月22日 18:59