06 月 15 日(Tue)のアレゲメモ
Flash でクロージャ。
Flash はクロージャが使えて便利です。 しかし、 最近はオブジェクト指向とか ActionScript 2.0 ばかり取り沙汰されて、 あまりクロージャの便利さが知られていない感じがします。 難しいオブジェクト指向の勉強はとりあえず先延ばしにしておいて、 気楽に使えるクロージャで楽しく ActionScript のプログラミングをしてみませんか?
お題:N フレーム目に○○せよ
たとえば、 Flash のムービーを再生して 5 フレーム後に、 指定したコードを走らせるには、 どうすればいいでしょうか?
ムービークリップの第 5 フレームに、 実行させたいアクションを書けばいいだけです。
しかしこの方法は、 問題をちょっと一般化するだけで使えなくなります。 この 5 フレームの「5」という数値が予めわかっていない場合は、 上の方法は使えませんね。
onEnterFrame
では「5 フレーム目」の代わりに「N フレーム目」としましょう。
この N が実行時まで分からなくても、 内部にフレーム数をカウントする仕組みを入れれば簡単です。 たとえば onEnterFrame プロパティを使って、 次のようにイベントハンドラを作ることができます
var count = N;
onEnterFrame = function () {
count --;
if (count < 0) {
doSomething (); // N フレーム目にやりたい処理
onEnterFrame = null; // もうこのイベントハンドラは実行されない
}
}
ここで、 count という変数を用意し、 onEnterFrame が呼ばれるたびに、 これをデクリメントしてカウントしています。
大域変数は使いたくない
しかし上記のような方法だと、 複数のムービークリップに対して、 それぞれに「N フレーム目に○○」したいという要求がある場合に、 その数だけカウンタ変数を用意する必要があり大変です。 たとえば、次のようになるでしょう。
var count1 = N1;
var count2 = N2;
mc1.onEnterFrame = function () {
count1 --;
if (count1 < 0) {
this.doSomething ();
this.onEnterFrame = null;
}
}
mc2.onEnterFrame = function () {
count2 --;
if (count2 < 0) {
this.doSomething ();
this.onEnterFrame = null;
}
}
ほとんど同じコードなのに、 参照するカウンタ変数が違うために、 コピペを余儀なくされてしまいます。
それに、 カウンタ変数の宣言と、 それを使うイベントハンドラのコードが離れて存在しているため、 イベントハンドラ以外のコードからカウンタ変数の参照や変更が出来てしまうし、 数が増えればそれだけややこしくなってしまいます。
また、 Flash の不可解なところで、 イベントハンドラ内に書く「this」は、 そのイベントを発生するムービークリップそのものを指すので、 その点も注意が必要です。
クロージャ
以上のような問題を解決するのがクロージャという仕組みです。 いきなり結果から書くと、 次のようなコードになります。
mc1.onEnterFrame = doSomethingAfterNFramesFunction (mc1, N1);
mc2.onEnterFrame = doSomethingAfterNFramesFunction (mc2, N2);
function doSomethingAfterNFramesFunction (mc, n) {
var count = n;
return function () {
count --;
if (count < 0) {
mc.doSomething ();
mc.onEnterFrame = null;
}
};
}
だいぶコードが短くなりました。 しかし、 function の中にさらに function があったりしてちょっと難しく感じるかもしれませんが、 これを決まりきったやり方なので覚えてしまってもよいと思います。
doSomethingAfterNFramesFunction() の中で、 return の直後に書いてある function は、 無名関数と呼ばれるものです。 これは、 動的に関数を作って返します。
doSomethingAfterNFramesFunction() では mc、n という仮引数と count という局所変数が宣言されています。 これらの変数は、 return する無名関数の中から参照できます。 それに加え、 これらの 3 つの変数は局所変数であるために、 doSomethingAfterNFramesFunction() の外から参照することは出来ません。
つまり、 これらのデータは、 doSomethingAfterNFramesFunction() が返す無名関数と一緒に カプセル化されたことになります。 この、 データと関数をひとまとめにしてカプセル化したものを、 クロージャといいます。
これは、 オブジェクト指向でいうところのオブジェクトと機能的には同じですが、 クラスを明示的に宣言したりする必要がない分気軽に使うことが出来ます。 データとコードの結びつきは、 クラスを定義して生成したオブジェクトよりも強いくらいです。
まとめ
上で見たように、 ムービークリップのイベントハンドラにクロージャをうまく使うことで、 複雑な処理を簡単に扱うことが出来るようになります。
普通、N カウント後に○○せよといわれたら、 単純に次のようなコードを思い浮かべると思います。
count = N;
for (;;) {
count --;
if (count < 0) {
doSomething ();
break;
}
}
しかし、 Flash の枠組みでは、 この一番外側の無限ループは ActionScript の手に届かないところに存在するので、 多くの人は、 グローバルな状態変数を導入し、 頭の中で状態遷移図を描き、 結果的にこれとは似ても似つかないコードを書いてしまうでしょう。
ところが、 クロージャを使った上の方法では、 無名関数の中身は上の for ループの中身とほとんど一致しています。 つまりクロージャを使えば、 問題を聞いて直感的に浮かんだコードに極めて近いやり方で、 ActionScript が書けてしまうという事です。
オブジェクト指向のような高級なやり方ももちろん面白いですが、 できるだけ難しいことを考えずに思ったとおりのコーディングをするためには、 クロージャは最適ですというお話でした。