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を!