目次
Javascriptでオブジェクト指向
Javascriptはプロトタイプベース(インスタンスベース、委譲による継承)なOOP。
JavaなどはクラスベースなOOP。
関数はクラス
function Foobar() {};
// 関数に new でインスタンスを生成できる。
// この関数はコンストラクタとして働く
var foobar = new Foobar();
alert( foobar.constructor ); // function Foobar() {}
alert( foobar instanceof Foobar ); // true
Javascript
では Java
のようなクラスは存在しないけど、関数をクラス的な存在として使うことができる。
クラスの定義
var Foobar = function(arg) {
// このような定義にすると new した全てのインスタンス
// 対してここで代入している内容が別々に作られてしまう。
this.var = arg;
this.method = function(str) {
alert(str);
};
};
// プロトタイプに定義することでコンストラクタ内で関数定義する
// ときのような問題点は解消される。
Foobar.prototype.pVar = 123;
Foobar.prototype.pMethod = function (str) {
alert(str);
}
// fooインスタンス自身が持つメンバがコールされる
var foo = new Foobar(999);
foo.method(foo.var); // 999
// プロトタイプに定義したメンバにアクセスする。
// これは foo.pMethod == undefined の場合は
// Foobar.prototype.pMethod にアクセスする仕様になっている為…
// (正しくは foo.__proto__.pMethod : new で生成されたインスタンスの
// __proto__ プロパティには インスタンスの prototype の参照が入れられる)
// foo.pVar == undefined なら foo.__proto__.pVar にアクセス。
foo.pMethod(foo.pVar); // 123
foo.__proto__.pMethod(foo.__proto__.pVar); // 上の1行と同じの結果
プロトタイプを利用する場合は、生成したインスタンスの分だけメソッドが生成されるというような無駄が生じない。
constructor
constructor
はコンストラクタを保持するためのプロパティ。
関数定義時に、その関数オブジェクトの .prototype.constructor
にその関数オブジェクトの参照が渡される。
constructor
はインスタンス自身が持っているプロパティではなく、関数オブジェクトの .prototype.constructor
をプロトタイプチェーンで読み取っている。
var Hoge = function() {};
alert( Hoge.prototype.constructor === Hoge ); // true
var instance = new Hoge; // HogeConstructor
// instance.__proto__ には Hoge.prototype が入っているため
// instance.constructor つまり instance.__proto__.constructor
// には Hoge が入っている。
alert( instance.constructor ); // Hoge.toString()の内容がアラート
constructor
からコンストラクタを辿れるとは限らない
function A(){};
function B(){}; // この時点では B.prototype.constructor === B だが…
B.prototype = new A; // ここで B.prototype.constructor === A になっている
var b = new B;
var bConstructor = b.__proto__.constructor;
alert(bConstructor === A); // true
alert(bConstructor === B); // false
プロトタイプチェーン
アクセスしたプロパティが未定義の場合は __proto__
が持つプロパティにアクセスする。
__proto__
のプロパティにアクセスしても未定義だった場合は、さらに__proto__.__proto__
にアクセスする…それでも未定義なら更に上の__proto__
にアクセスする。
この連鎖をプロトタイプチェーンという。
プロトタイプチェーンという言葉の中のプロトタイプが指すものは コンストラクタ.prototype
ではなくて、コンストラクタから渡されたインスタンス.__proto__
プロパティ(FireFoxなどの拡張仕様で本来はアクセスできない)ので間違えないように…というか思いっきり間違えていた。
// 親とするオブジェクト
MetaVar = function() {};
MetaVar.prototype.exec = function() {
alert("MetaVar");
};
Hoge = function() {
// 親のコンストラクタ実行
MetaVar.apply(this, arguments);
};
// Hoge に MetaVar を継承させる
Hoge.prototype = new MetaVar;
var instance = new Hoge();
instance.exec(); // alert「MetaVar」
/* instance.exec == undefined かつ
- instance.__proto__.exec == undefined だけど
- instance.__proto__.__proto__.exec != undefined なので
- instance.__proto__.__proto__.exec にアクセスする。
- この場合は MetaVar.prototype.exec と同じものにアクセスする。
*/
alert( instance instanceof MetaVar ); // alert「true」
instanceof
演算子もプロトタイプチェーンを辿る。
__proto__
に入るもの
IE
では使えないけど プロトタイプチェーンで辿っているのは__proto__
プロパティ(厳密には [[prototype]]
らしい)。
new
でインスタンスを生成した時に、そのインスタンスの__proto__
プロパティに コンストラクタ.prototype
の参照が代入される。
var A = function() {
this.piyo = null;
};
var a = new A;
alert( a.__proto__ === A.prototype ); // true
alert( a.__proto__ === a.constructor.prototype ); // true
alert( A.prototype === a.constructor.prototype ); // true
instanceof
と__proto__
function A(){};
function B(){};
B.prototype = new A;
var b = new B;
alert(b instanceof B); // true
alert(b instanceof A); // true
var buffer = b.__proto__.__proto__;
b.__proto__.__proto__ = null;
alert(b instanceof B); // true
alert(b instanceof A); // false
b.__proto__ = buffer;
alert(b instanceof B); // false
alert(b instanceof A); // true
buffer = A.prototype;
A.prototype = {};
alert(b instanceof B); // false
alert(b instanceof A); // false
A.prototype = buffer;
b.__proto__ = {};
alert(b instanceof B); // false
alert(b instanceof A); // false
b instanceof A
は A.prototype
と一致するものを b
のプロトタイプチェーンから探し、見つかれば true
を返す。
prototype
は別のオブジェクトに変更される可能性があるので…
new
直後は ( Class.prototype === instance.
proto) === true
であるものの、この関係が常に保証されるわけではない。
function A(){};
A.prototype = {};
var instance = new A;
alert(A.prototype === instance.__proto__); // true
A.prototype = {};
alert(A.prototype === instance.__proto__); // false
__proto__ === null
で止まる
インスタンスのプロトタイプチェーンは基本的には Object.prototype.__proto__
で止まる (Object.prototype.__proto__
は null
)
var A = function() {};
var o = new Object();
A.prototype = o; // Objectを継承
var a = new A;
// a.__proto__ には o が入っている
alert( a.__proto__ === o ); // true
// o.__proto__(a.__proto__.__proto__) には Object.prototype が入っている
alert( a.__proto__.__proto__ === Object.prototype );
// Object.prototype.__proto__(a.__proto__.__proto__.__proto__)
// には null が入っている
alert(a.__proto__.__proto__.__proto__); // null
alert(a.piyo); // undefined
__proto__
を辿って最終的に __proto__ == null
だった場合は undefined
が返される。
謎の Object.__proto__
Object.__proto__
にも何らかのオブジェクトが入っている。
var enigma = Object.__proto__; // 謎のオブジェクト
alert( enigma ); // 空の関数
alert( enigma.__proto__ === Object.prototype ); // true
alert( enigma.__proto__.__proto__ ); // null
これは何もしない空の関数で __proto__
プロパティを持ち、この__proto__
の中身は Object.prototype
のものと同じ。
var A = function() {};
alert( A.__proto__ === Object.__proto__ ); // true
alert( A.__proto__ === Array.__proto__ ); // true
Function
や Array
の __proto__
にもコレが入っているらしい。
プロトタイプに関する参考リンク
- プログラマのためのJavaScript (7):プロトタイプ継承の正体 - 檜山正幸のキマイラ飼育記
prototype
と__proto__
- フリーフォーム フリークアウト- プロトタイプチェーンの図とか: いげ太のブログの図がとてもわかり易かった。
アクセサ
var xxx = {
yyy : undefined,
get zzz () { return this.yyy; },
set zzz (arg) { this.yyy = arg; }
};
alert(xxx.yyy); // undefined
alert(xxx.zzz); // undefined
xxx.zzz = "piyopiyo";
alert(xxx.yyy); // piyopiyo
alert(xxx.zzz); // piyopiyo
alert(xxx.zzz = "hogehoge"); // hogehoge
// このスクリプトは動かない
var ppp = {
qqq : undefined,
get qqq () {} // フィールドと同じ名前はエラー
};
get, set
でゲッター・セッターの定義はできるものの、隠蔽できるわけではない。
オーバーライド?
プロトタイプチェーンを利用することでオーバーライドっぽい動作を実現できる
プロトタイプチェーンを使用しない場合
ただ単に関数オブジェクトを別のものにするだけでも似たようなことはできる。
// ただ単に関数自体を別物にするパターン
var f;
f = function() {
alert("HOGE");
};
f(); // HOGE
f = function() {
alert("PIYO");
};
f(); // PIYO
ただし、この場合は最初の関数は消えてしまう。
※これをオーバーライドと呼べるのかは知らない
プロトタイプチェーンの利用
var Super = function(isAlert) {
// コンストラクタ
if ( isAlert === false ) {
alert("SuperConstructor");
}
};
Super.prototype.func = function() {
alert("Super.prototype.func");
};
var Sub = function() {
// スーパークラスのコンストラクタを呼び出す
Super.apply(this, arguments);
// 派生クラスのコンストラクタ
alert("SubConstructor");
};
Sub.prototype = new Super(false);
Sub.prototype.func = function() {
alert("Sub.prototype.func");
};
var obj = new Sub();
obj.func(); // Sub.prototype.func
// 削除すると…
delete Sub.prototype.func;
obj.func(); // Super.prototype.func
var Super = function() {
this.number = 123;
};
Super.prototype = {
number : 123,
getNumber : function() {
return this.number;
}
};
var A = function(number) {
if ( number != null ) {
alert( "number changed" );
this.number = number;
}
};
A.prototype = new Super();
A.prototype.getNumber = function() {
return this.number * 10;
};
alert( new A().getNumber() ); // 1230
alert( new A(987).getNumber() ); // number changed -> 9870
var B = function() {
this.number = 567;
};
B.prototype = new Super();
alert( new B().getNumber() ); // 567
よく分からない prototype
の挙動
var A = function() { alert("A"); };
A.prototype.hoge = "tesu";
var B = function() { alert("B"); };
B.prototype = { hoge : "tesu" };
alert( new A().constructor );
alert( new B().constructor );
new A().constructor の結果が function() { alert("A"); }
なのに
new B().constructor の結果が function Object() { [native code] }
になってしまう…なんで???
おかげで以下のようなエラーが出ることがある。
var A = function() {};
A.prototype.exec = function() { alert("A"); };
var B = function() {};
B.prototype = {
exec : function() { alert("B"); }
};
var C = function() {};
C.prototype.exec = function() { alert("C"); };
C.prototype = new A();
new C().constructor.prototype.exec.call(C); // 「A」とアラートが出る
C.prototype = new B();
new C().constructor.prototype.exec.call(C); // execメソッド無いとブラウザ激怒
スーパークラスのメソッドを呼べないじゃん… 何が起きてるんだろう。いろいろと気持ち悪い。 誰か教えてください orz
上のコードでエラーが出る理由
コメントを頂いて理解できた! コメントして頂いたひよこさん、解説ありがとうございました!
A = function(){};
B.prototype = new A();
B.prototype = {
doSomething : function(){/*何かする*/};
};
C.prototype = new Object();
C.prototype.doSomething = function(){/*何かする*/}
// B と C は意味的には等価 (もちろん別インスタンスという違いはある)
上のコードで B
と C
が等価になるのは、B.prototype = { /\* 略 \*/ }
としてしまうと new A()
で生成したインスタンスの代わりに { /\* 略 \*/ }
が入ってしまうので、new A()
で生成したインスタンスは使われず無意味に消えてしまうから。prototype
プロパティはただのオブジェクトに過ぎない。結果的には B
は Object
を継承したコンストラクタになってしまい A
は無関係のものとなってしまう。当然 B
に継承されたオブジェクトがあってもアクセス不能になる。
インターフェース
Javascript
の機能としてインターフェースは存在しないので、インターフェースっぽいものを勝手に実装して下さいってことらしい。
参考:JavaScript
でJava
のInterface
を実現する方法 - yinkywebの日記
カプセル化
var Class = (function() {
// privateっぽいもの
// この環境でのみアクセス可能
var privateField = "private!!!";
var privateMethod = function() {
alert(privateField);
};
var clazz = function() {
alert("コンストラクタ");
};
clazz.prototype.publicMethod = function() {
privateMethod();
};
return clazz;
})();
var obj = new Class();
obj.publicMethod();
obj.privateMethod(); // エラー
可視性を指定するものなんて無いのでクロージャで隠蔽する。protected
なものって作れるのかな。
ってかこのコードだと static private
な感じの中途半端なものになってしまう…どうしよう
var Class = (function() {
var num = 0;
var C = function() {};
C.prototype.func = function() {
alert(++num);
};
return C;
})();
var a = new Class();
var b = new Class();
a.func(); // 1
b.func(); // 2
alert(a.m); // undefined
num
は インスタンスメンバ的なものになるかと思ってたけど…
ここで num
は static
メンバ的な動作になってしまう…駄目じゃん orz
だよなぁ…隠蔽してかつ static
ではない private
をやろうとすると…
どうなるんだってばよ?
var Class = (function() {
/*
- この var は static private な動作をする
*/
var staticPrivateNum = 0;
// クラス
var clazz = function() {
/*
- この var は private な動作をする
*/
var privateNum = 0;
// インスタンスごとに別のprivateNumに
// アクセスさせようと思うと
// prototype ではないプロパティに
// 関数定義しないと無理だよな…
// new毎にメソッドができてしまうのはしかたがないのか…
this.func1 = function() {
alert( ++privateNum );
};
// これはダメ…
// 最後に new Class したときにできた
// privateNum にアクセスしてしまう
clazz.prototype.func3 = function() {
alert( ++privateNum );
};
};
// staticメンバにアクセスするものであれば
// prototype に定義すればOK
clazz.prototype.func2 = function() {
alert( ++staticPrivateNum * 100 );
};
return clazz;
})();
var a = new Class();
var b = new Class();
a.func1(); // 1
b.func1(); // 1
a.func1(); // 2
b.func1(); // 2
a.func1(); // 3
b.func1(); // 3
alert( a.privateNum ); // undefined
a.func2(); // 100
b.func2(); // 200
a.func2(); // 300
b.func2(); // 400
a.func2(); // 500
b.func2(); // 600
alert( a.staticPrivateNum ); // undefined
a.func3(); // 4
b.func3(); // 5
b.func1(); // 6
うーん…なんか微妙。
参考 : Private Members in JavaScript
Wordpress
時のコメントログ
ひよこ より: 2012年9月10日 12:04 PM
var A = function() {};
// この時 A.prototype.constructorに”自動で”Aが代入される。
A.prototype = {methodA: function(){}};
// prototype にオブジェクトを代入することで、
// 自動で代入されたA.prototype.constructorはA.prototypeごと上書きされる。
A.prototype = {constructor: A, methodA: function(){}};
// もしconstructorプロパティを利用するなら、このようにしておく
var B = function() {};
B.prototype = new A(); // EcmaScript5な環境ならば Object.create(A.prototype); とするべき
var b = new B();
// このとき b.constructor が A になるのは new A() のconstructorプロパティを読んでいるから
// b.constructor は A ではなく B を返すのが正しい(と思う)ので、
B.prototype = new A();
// の後で
B.prototype.constructor = B;
// としておいたほうがいいかもしれない。
// C は A を継承して、 methodAを書き換える
var C = function(){};
C.prototype = {constructor: C, methodA: function(){ /\* override \*/ }};
var c = new C();
// もし C の methodA ではなくて A のmethodAを呼び出したいなら
// constructorプロパティは利用せず
A.prototype.methodA.call(c);
// のようにする。
// constructorプロパティは文字通りコンストラクタを保持するプロパティ。
// 本来は、継承元との関係はない。
Fernweh より: 2012年9月10日 11:09 PM__proto__
が使えない場合にどうやって親にアクセスするのかと思いましたが、親.prototype.メソッド.call(インスタンス);
のように 親オブジェクトを直接指定するのですね。これをやりたくなくて constructor
から呼び出していたのですが、ひよこさんの説明を見ていろいろ勘違いしすぎていることが分かりました orz
とはいえコメントのおかげでどんな挙動をしているのかなんとなく分かりました。
わかり易い説明をしていただいてとても嬉しいです。