ようこそゲストさん

adiary開発日誌

お知らせ

絶対使わないと言い切れますか? blog + wiki = adiary

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

1: Yorkfield 2010年02月06日(土) 深夜4時49分

> オブジェクト(っぽいもの)の生成
・JavaScriptは大小文字を区別するのでそのままだとエラーになってしまいます。
・メンバ関数add/subはメンバ変数を変更してないので、
 メンバ変数countはずっと初期値のままになってしまいます。

まあ、それはおいておいて、
JavaScriptでクラスを作るならprototypeを利用することが多いと思います。

function Num() {
 // メンバ変数
 this.count = 0;
}
Num.prototype.add = function(x) {
 return (this.count = this.count+x);
}
Num.prototype.sub = function(x) {
 return (this.count = this.count-x);
}
Num.prototype.get = function() {
 return this.count;
}

> メンバ関数をコールバックさせる 方法1
prototype.jsのbindメソッドやbindAsEventListenerが
thisや引数を束縛するためのメソッドなので参考になると思います。

> メンバ関数をコールバックさせる 方法2
関数への参照をイベントハンドラとして使いたいなら、
HTMLを書き出すよりもDOMで処理した方がいいかも。

http://wiki.bit-hive.com/tomizoo/pg/JavaScript%20%A5%A4%A5%F3%A5%E9%A5%A4%A5%F3%A5%D5%A5%EC%A1%BC%A5%E0(iframe)%B4%D8%CF%A2

2: なべ 2010年02月09日(火) 深夜1時19分

>・JavaScriptは大小文字を区別するので
>・メンバ関数add/subはメンバ変数を変更してないので、
しまった。単純なエンバグでした。感謝。

>prototypeを利用することが多いと思います。
たしかに。自分もprototypeを利用しています。
ただ見づらくなるので、関数代入でもいいかなみたいな感じです。
関数の直接記述だとオブジェクトごとに関数が作られるのでイマイチですが。

>prototype.jsのbindメソッドやbindAsEventListenerが
>thisや引数を束縛するためのメソッドなので参考になると思います。
可変引数対応で汎用的なのかあ。
やってることは変わらないけど、たしかにより汎用的ですね。

>> メンバ関数をコールバックさせる 方法2
>関数への参照をイベントハンドラとして使いたいなら、
>HTMLを書き出すよりもDOMで処理した方がいいかも。
その方が綺麗なんですけどね。
document.writeする方が楽だからという(苦笑)
ifreame1個ぐらいならDOMでも大した手間じゃないですが、
いくつも処理すると手間だなーという手抜き。

でも微妙な仕様なのは確かだから、
prototype.js参考にしてDOMに書きなおそうかあ。

色々ツッコミありがとうございます。


名前:   

  • TB-URL  http://adiary.blog.abk.nu/0265/tb/