3.0イベントリスナーに登録する関数で、たとえばその登録のときに指定した値を使いたいことがあります。その手法をいくつかご紹介して説明します。リスナー関数に引数を渡すのではないリスナー関数にどうしたら引数が渡せるのか、と問われることもときおりあります。それは大抵の場合、問題の立て方を誤っています。Playerであり、引数はイベントオブジェクトひとつです。リスナー関数に引数を渡そうとするのは、店から直送されるお中元に、手書きのメッセージを添えたいというのと同じです。商品を送るのはお店で、メッセージは自分の手元です。そのふたつに接点がないのですから、同封しようがありません。してもやりたければ、品物を自分のもとに送ってもらって、メッセージを入れたうえで、改めて発送するしかないでしょう。3.0では、中継のクラスを定義したうえでイベントリスナーに登録し、そのクラスから改めて関数に引数を渡すか、カスタムイベントを配信するという設計になります。イベント配信の流れまで変えたいというのではなく、予め指定した値をリスナー関数で利用できれば済むようです。関数クロージャ前項01のリスナー関数に引数を渡したいという問いに対する答えとして、よく使われるのがいわゆる「関数クロージャ」を使う手法です[*1]。これは、設定用に定められた関数の中で、渡したい値をローカル変数に納め、リスナーとして用いる入れ子の関数を定義して返します。つぎのフレームアクションは、Stageオブジェクトへのマウスクリックに対するイベントリスナーを、関数(getListener())から受取って登録します。関数(getListener())には渡す値としてDateインスタンスを引数に指定し、入れ子で定義した名前のない関数が戻り値になっています。ステージをクリックすると、イベントリスナーを登録するときにつくったDateインスタンスの文字列表現が[出力]されます。2010}}スクリプトの行数は少なくて済み、関数に引数を渡している「気分」がウケているようです。上述のとおり、値はイベントが発生(クリック)したときではなく、リスナーを登録したときに設定されたものです。これから述べるように、クセや注意点が少なくないので、とくに必要がある場合でないかぎり、お使いになることはあまりお勧めしません。第1に注意しておきたいのは、イベントリスナーに登録した入れ子関数(クロージャ)におけるthis参照です。thisキーワードは、関数やメソッドが定義されたオブジェクト(クラスのインスタンス)を参照します。上記フレームアクションでは、trace()関数の第1引数にthisを指定しました。global]は、グローバルオブジェクトを示します[*2]。これは、入れ子関数(クロージャ)が、特定のオブジェクトに定義されていないことを意味します。根なし草の関数なのです。第2は、クロージャの意味と働きです。上記フレームアクションでは、イベントリスナーに入れ子関数を登録しました。入れ子関数は親関数(getListener())の引数(param)を[出力]しています。それができるのは、入れ子関数の参照には、親関数の引数やローカル変数が含まれているからです。このように関数とその定義された環境をひとつのデータとして扱うのが「クロージャ」という仕組みなのです(注[*1]参照)。使いでがあるのは、同じシンボルのインスタンスを複数タイムラインに置いて、それぞれに異なった値を設定しつつ、リスナー関数の処理は同じにしたいというときでしょう。3つのインスタンス(instance0〜instance2)にそれぞれ異なった整数(0〜1)を設定して、インスタンスがクリックされたらその値を[出力]してみます。その場合、つぎのようなforループの処理で、すっきりとまとめられます。param);}}しかし第3の注意は、見た目はすっきりでも、中身はもっさりだということです。上記forループの処理を実際に書出すと、つぎのようなスクリプトとほぼ同じになります。param);});リスナー関数が、インスタンスと同じ数だけつくられています。目的が「それぞれに異なった値を設定しつつ、リスナー関数の処理は同じにしたい」のだとすれば、リスナー関数はひとつで済ませるのが筋でしょう。第4にもっとも大きな問題が、関数の中で定義されたクロージャは、関数の外から参照できないことです[*3]。具体的にはこのままでは、イベントリスナーが削除できません。そのためには、リスナー関数の参照が必要になるからです。ただでさえメモリを無駄にくううえに、消せなくなるのは避けなければなりません。リスナー関数の参照は、別にとっておくべきです。つぎのフレームアクションは、リスナー関数を配列(listeners)に納めています。イベントが起こると、配列からそのリスナー関数の参照を取出して削除します[*4]。listeners[param];}}第5につけ加えるなら、クロージャに含まれるローカル変数(引数)も、外からアクセスできません[*5]。したがって、その値は後から変えられないということです。[*1]「関数クロージャ」という用語は、あまり広くは使われないようです。一般には、単に「クロージャ」(closure)と呼ばれ、関数とそれが定義された環境(関数の定義されたオブジェクトや関数外のActivationオブジェクトなど)を一体として扱うデータ構造だとされます。Japanese「Closure」などをご参照ください。[ActionScript言語とシンタックス]「関数のスコープ」の「スコープチェーン」には、つぎのように説明されています。グローバルオブジェクトは、ActionScriptプログラムが起動すると作成され、すべてのグローバル変数および関数を含みます。実は、もうひとつ大きな問題を引き起こしかねないことがあります。本文にも述べたとおり、クロージャには親の関数のローカル変数(引数)が含まれます。それは、入れ子関数で使うか使わないかを問いません。要らないビットマップのデータやエレメントの多い配列、あるいは大量の文字列などがローカル変数に納められていれば、クロージャの中に参照が残ってしまうのです。ループ処理などしていたら、その回数分データが複製されます。この問題については、www.imajuk.swf「アクティベーションオブジェクトによるメモリリーク」が詳しいです。リスナー関数自身からイベントリスナーを削除するのであれば、関数の参照はarguments.calleeプロパティで得られます。関数が呼出されると、その実行の間引数やローカル変数を納めるActivationオブジェクトがつくられます。前出注[*2]の「スコープチェーン」は、つぎのように説明します。最初に、アクティベーションオブジェクトという特別なオブジェクトが作成され、そこには関数本体で宣言されるパラメーターとローカル変数または関数が格納されます。アクティベーションオブジェクトは内部メカニズムであるため、そこに直接アクセスすることはできません。この説明でActivationオブジェクトに格納される「関数」というのが、入れ子の関数を指すと考えられます。リスナー関数はひとつで済ますインスタンス「それぞれに異なった値を設定しつつ、リスナー関数の処理は同じにしたい」というお題で、リスナー関数はひとつで済ませましょう。前述のスクリプトと同じく、インスタンスはタイムラインに置いた3つ(instance0〜instance2)で、それぞれに異なる整数(0〜1)を割当てるものとします。インスタンスがMovieClipなら簡単です。MovieClipインスタンスには、いくらでも変数が設定できるからです。つぎのフレームアクションのように処理すればよいでしょう。インスタンスをクリックしたらリスナー関数を呼出し、イベントリスナーからは削除します。listener);}つぎに、ボタンシンボル(SimpleButtonクラス)やSpriteクラスなど、多くのクラスでは変数(プロパティ)が勝手に加えられません。すると、変数はフレームアクションで別に定めることになりそうです。できれば、配列などにまとめられると扱いやすいでしょう。配列からインスタンスごとに異なったエレメントを取出すには、それぞれに異なったインデックスをもたせなければなりません。インスタンスごとに異なった値が設定できないから困っているので、これでは堂々巡り、童謡「バケツの穴」状態です。インスタンスごとに違うものはあります。インスタンス名(instance0〜instance2)です。文字列の名前(DisplayObject.nameプロパティ)をキーにして値をまとめるには、Objectインスタンスが使えます。以下のフレームアクションは、3つのインスタンス名の文字列に対してそれぞれ設定した値を、Objectインスタンスに加えています。リスナー関数は、イベントを処理するインスタンス名からObjectインスタンスに納められた値を取出して[出力]します。3.0では文字列のインスタンス名は、扱いやすいとはいえず、また応用の幅が広くありません[*6]。インスタンスの名前でなく、参照そのものを用いることが、できれば望ましいでしょう。参照をキーとして値をまとめられるのは、Dictionaryクラスです。Dictionaryクラスの使い方について、詳しくは「Dictionary()コンストラクタ」をお読みください。具体的には、上述のスクリプトで、Objectインスタンスの生成をDictionaryに替え、インスタンスからの値の取得は名前でなく参照をキーにします。listener);}これだけでは、ObjectとDictionaryクラスを使う違いが、さほどないように思えるかもしれません。インスタンスをタイムラインに動的に置くときは、とくに必要がないかぎり名前はつけないでしょう。Objectクラスを使った場合、インスタンスからいちいち名前を取出さないと値が得られません。以前のバージョンのActionScriptとの違いも含めて、F-site「MovieClipインスタンスとインスタンス名」をご参照ください。中継のクラスから関数に引数を渡す前項03の手法で、おそらく課題は解決できると思います。しても引数を渡したいという場合について、試みに考えてみます。01で述べた「中継のクラスを定義したうえでイベントリスナーに登録し、そのクラスから改めて関数に引数を渡す」手法です。つぎのような中継のクラスPassingParamToListenerを定義しました。this);}}}}このクラスの使い方は、つぎのようになります。第1に、コンストラクタメソッドに、渡したい値(myParam)と呼出す関数(myListener)を引数として渡します。第2に、イベントリスナーにはこのPassingParamToListenerクラスのインスタンスメソッドcallListener()を登録します。すると、リスナーメソッドcallListener()が中継して、指定された関数に引数を渡して呼出すという仕組みです。caller.callListener);タイムラインに置かれた3つのインスタンス(instance0〜instance2)のお題に当てはめるなら、以下のフレームアクションのようになります。caller.callListener);}PassingParamToListenerクラスのコンストラクタに渡した関数は、呼出されるとき3つの引数を受取ります。第1引数はイベントオブジェクト、第2引数がコンストラクタに指定した値、そして第3引数はPassingParamToListenerインスタンスです。この第3引数を用いれば、上記のとおりイベントリスナーが削除できます[*7]。PassingParamToListenerインスタンスはあえて参照を残していませんので、イベントリスナーから削除されればガベージコレクトを待つことになります。