2010/01/30(土)JavaScriptでオブジェクト指向なメモ
適当に追記します。
雑多なおさらい
- JavaScriptのクラスの概念はないらしい。
- プリミティブな数字や文字列など意外は、すべてオブジェクトであり参照。
- 参照なので代入した側でメンバ変数をいじると、代入元も書き換わる。
- 関数を変数に代入できる。
var f = function (x){ alert(x); } f("msg"); // "msg"と表示される
Perl5のオブジェクトに似たところがありますが、クラス名前空間(パッケージ)を持たないため、Perl5よりも簡素な実装と言えそうです。
オブジェクト(っぽいもの)の生成
Numというクラスっぽいものです。
function Num() {
this.add = Num_add;
this.sub = Num_sub;
this.get = Num_get;
// メンバ変数
this.count = 0;
}
function Num_add(x) {
this.count+=x;
return this.count;
}
function Num_sub(x) {
this.count-=x;
return this.count;
}
function Num_get() {
return this.count;
}
使い方。
var num = new Num(); num.add(10); num.sub(5); alert( num.get() ); // 5と表示される
- newによって生成されたオブジェクトへ Tag() によってメンバ関数(メソッド代わり)を代入し、メンバ変数を初期化する。実のところ、変数に関数(の参照)が代入できるので、単なる代入でしかない。
- 「num.add()」のようにしてメンバ関数を呼び出すと、呼び出された側はthisでオブジェクトを参照できる。
- メンバ関数は Num() の中で宣言すると名前空間を汚さないが、見づらくなるので……。
メンバ関数をコールバックさせる 方法1
コールバックさせたいときに、どうやってオブジェクトを渡すか。
タイマーの場合。
function Class() {
this.Run = Class_Run;
this.DoRun = Class_DoRun;
}
function Class_Run() {
setTimeout(this.DoRun, 1000);
}
funciton Class_DoRun() {
alert("DoRun()");
// 1秒後に実行されるが、this を参照できない。
}
// 実行
var obj = new Class();
obj.Run();
この例のように時間差でメンバ変数を実行させたり、onreadystatechange による非同期実行などのコールバックは this を参照できません。onreadystatechange はAjaxで盛んに使われますから、これは厄介な問題です。
これを解決するためにクロージャというものを使い、Class_Run()を次のように書き換えます。
function Class_Run() {
var obj = this;
var func = function () {
obj.DoRun();
};
setTimeout(func, 1000);
}
変数 func に無名関数を代入し、それを呼ばせることでオブジェクト obj=this を保持させています。クロージャの概念については深入りしませんが、関数の中に変数の値ごと閉じ込めてしまうという感覚です。
- 閉じ込める変数はvar宣言されたローカル変数でなければならない。
- ローカル変数でない場合はグローバル変数の参照とみなされクロージャの中にそのときの値を閉じ込めることができない。
- より正確にはvarで宣言した変数がスコープから外れるときに初めて値が閉じ込められます。
変数 obj に this を代入しているところがミソであるわけです。クロージャを使えば、引数なども閉じ込めることができます。
メンバ関数をコールバックさせる 方法2
関数を参照として呼び出すケースだけならば方法1ですべて足りるのですが、残念ながらそればかりとは限りません。
function Class() {
this.Run = Class_Run;
this.DoRun = Class_DoRun;
}
function Class_Run() {
document.write('<ifreame id="frame" src="data.html" onload="DoRun()">');
}
funciton Class_DoRun() {
var frame = document.getElementById("frame");
// iframeでロードした data.html の中身を参照するため、
// iframeがロードされた後でないと実行できない。
}
// 実行
var xobj = new Class();
xobj.Run();
data.html にデータが記述されており、data.html の中身をJavaScriptから参照することでうまく処理をしたいところですが、このままでは CallbackRun() からは this を参照することができません。オブジェクト指向としては致命的です。似たケースとして selectbox や checkbox の変更(onChange="")によって処理をさせたいときもあります。
やや強引な方法ではありますが、生成時にグローバルなオブジェクトの変数名を登録してしまえば解決します。
function Class(g_name) {
this.Run = Class_Run;
this.DoRun = Class_DoRun;
// グローバルで参照できる変数名
this.g_name = g_name;
}
function Class_Run() {
document.write('<ifreame id="frame" src="data.html" onload="'+this.g_name+'.DoRun()">');
//「<ifreame id="frame" src="data.html" onload="xobj.DoRun()">」と出力される
}
funciton Class_DoRun() {
var frame = document.getElementById("frame");
// 今度は xobj.DoRun() として呼ばれるため、thisを参照できる
}
// 実行
var xobj = new Class('xobj'); // xobjという変数に入ってることを知らせておく
xobj.Run();