JavaScriptでクラス利用時にActionScript2の委譲クラス(Delegate)を移植した話

JavaScriptでクラス利用時などでスコープに手間取ったので、ActionScript2の委譲クラス(Delegate)を移植してみました。

クラス構造を使うと今までのActionScriptの資産も活かせるので積極的に使うようにはしているのですが、ここのところ、CreateJSでのイベント、enchant.jsのaddEventListenerなどでスコープの範囲で手間取るのでActionScript2の委譲クラス(Delegate)を移植してみました。

例えばこんなコード

フレームワークのコードで説明するとどうしても煩雑になってしまうので、今回はシンプルにボタンオブジェクトのonclickの例を挙げます。

こんなコード(Dogクラス)の実装があります。

ひとまずクラスを呼び出して使う

普通にクラスを呼び出してDogにwanwanさせてみます。まだボタンアクションはなく表示すぐに実行。

別ページで開く

クラスはこんな感じ。「わんわん!」をthis.messageで喋らせているのに注目ください。

// いぬクラス
var Dog = function (){};
Dog.prototype = {
	message:"わんわん!",
	wanwan: function() {
		document.getElementById("debug").value = "this.message : " + this.message;
	}
}

window.onload = function(){
	// いぬクラスの呼び出し
	_dog = new Dog();
	// actionを実行すれば変数messageが表示。
	_dog.wanwan();  // this.message : わんわん!
};

図に書くとこんな感じ。

ボタンで動くようにしてみる

では、ボタンを押して動くようにしてみます。コードはこんな感じ。

// いぬクラス
var Dog = function (){};
Dog.prototype = {
	message:"わんわん!",
	wanwan: function() {
		document.getElementById("debug").value = "this.message : " + this.message;
	}
}

window.onload = function(){
	// いぬクラスの呼び出し
	_dog = new Dog();
	// ボタンのonclickイベントに関数actionを指定。
	btnNormalElement = document.getElementById("btn");
	// Dogクラスの変数messageが表示されるはずがundefined。
	btnNormalElement.onclick = _dog.wanwan; // this.message : undefined
};

ActionScript3などで慣れていると、先ほどと単純に呼び出せした時と同じようになるのと思います。

予想図。

しかし、実際にはうまくいかない。

しかし、実際にはundefinedがでてthis.messageが参照できません。

別ページで開く

なぜ呼べないのか検証してみる

見た目上、問題無さそうなのに、なぜ呼べないのか検証してみます。試しにthisを表示してみる。

	wanwan: function() {
		// 試しにthisを表示してみる
		document.getElementById("debug").value = "this : " + this;
	}

と書いたのがコチラ。

別ページで開く

actionを押してみると「this : [object HTMLInputElement]」と表示されます。

さらにChromeのデバッグツールなどで細かく見てみると、このthisが示すオブジェクトはonclickで指定したボタンエレメントそのものです。

this.valueをしてみると・・・

ですので、this.valueをしてみると・・・。ボタンの名前が「action」と表示されます。いや、JavaScriptの挙動としては正しいのでしょうが、ちょっとどうにかしたい。

	wanwan: function() {
		// ボタンの名前が表示
		document.getElementById("debug").value = "this.value : " + this.value;
	}

別ページで開く

そんなわけでDelegate登場

そんなわけで、ActionScript3のように、元のクラスの影響下がずっと続くスコープ感覚だと戸惑います。なるべく似たように書きたい。

ということで、私の場合は、このJavaScriptの挙動がActionScript2のそれに近かったので、以前のDelegateクラスを引っ張りだして以下のように実装しました。


// AS2で使っていた委譲クラスを移植
var Delegate = function (){};
Delegate.prototype = {

	create : function ( __target, __func )
	{
		var f = function()
		{
			// f._targetから渡してもらった実行場所の参照
			// 「この場所で実行されたことにしたいよ。」
			var target = arguments.callee._target;
			// f._funcから渡してもらった、動作対象の関数そのもの
			var func = arguments.callee._func;
			// Function.apply
			return func.apply(target, null);
		};
		
		f._target = __target;
		f._func = __func;
		
		return f;
	}
	
}

// いぬクラス
var Dog = function (){};
Dog.prototype = {
	message:"わんわん!",
	wanwan: function() {
		document.getElementById("debug").value = "this.message : " + this.message;
	}
}

window.onload = function(){
	
	// Delegateクラス呼び出し
	_delegate = new Delegate();
	
	// 通常のいぬクラスの呼び出し
	_dog = new Dog();
	// ボタンのonclickイベントに関数actionを指定。
	btnElement = document.getElementById("btn");
	// 
	btnElement.onclick = _delegate.create( _dog , _dog.wanwan ); // this.message : わんわん!
};

Delegateの動作イメージはこのようになっています。

動作結果です。

別ページで開く

わんわん!と表示されました!ちゃんと、動きますね。

めでたしめでたし。

参考文献

文中では説明しきれなかったDelegateやスコープについての情報は以下に詳しく書いています。参考にしてみてください。

無気力関数 – Javascriptのスコープとdelegate

fladdict.net blog: JavaScriptでthisスコープをコントロールする

JavaScriptの再利用とapply | 勉強するのが、そんなに偉い訳!?

JavaScript のブロックスコープと名前空間 | Mozilla Developer Street (modest)

おわりに

いかがでしたでしょうか。

JavaScriptにおけるクラスとスコープについてはなかなか悩ましいですが、今回はDelegateという手法でまとめてみました。コーディングの一助となれば幸いです。

それでは、よきJavaScript Lifeを!