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();