AS3のクロージャ

どうしても解消しないメモリリークを追いかけていったら、アクティベーションオブジェクトにたどりついたので、
自分の理解を深めるためにまとめてみます。
長くなりそうなので、3回のエントリーに分けます。

  1. AS3のクロージャ
  2. アクティベーションオブジェクトとスコープチェーン
  3. アクティベーションオブジェクトによるメモリリーク

関数

リファレンスによると、AS3の関数は「メソッド」と「関数クロージャ」の 2種類に分類されるようです。

There are two types of functions in ActionScript 3.0: methods and function closures.
ActionScript 3.0 の関数には「メソッド」と「関数クロージャ」の 2 種類があります。
(ActionScript 3.0 のプログラミング / ActionScript 言語とシンタックス / 関数)

関数クロージャとは何かというと、メソッド以外の全ての関数のことです。

Whether a function is a called a method or a function closure depends on the context in which the function is defined. A function is called a method if you define it as part of a class definition or attach it to an instance of an object. A function is called a function closure if it is defined in any other way.
関数をメソッドと呼ぶか関数クロージャと呼ぶかは、関数が定義されたコンテキストによって決まります。関数をクラス定義の一部として定義した場合、またはオブジェクトのインスタンスに関連付けた場合は、メソッドと呼びます。関数がその他の方法で定義された場合は、関数クロージャと呼びます。

さらに、メソッドは特にメソッドクロージャ(バインドメソッド)と呼ばれる使われ方をすることがあります。

A bound method, sometimes called a method closure, is simply a method that is extracted from its instance. Examples of bound methods include methods that are passed as arguments to a function or returned as values from a function.
バインドメソッドは、単にインスタンスから抽出されるメソッドで、「メソッドクロージャ」とも呼ばれます。バインドメソッドの例には、関数にパラメータとして渡され、関数から値として返されるメソッドがあります。
(ActionScript 3.0 のプログラミング / ActionScript のオブジェクト指向プログラミング / クラス)

クロージャ

つまり、AS3のクロージャには「メソッドクロージャ」と「関数クロージャ」があるということですね。
関数の分類を図にするとこんな感じです。
function.jpg


もうちょっと詳しくみていきます。
例えば以下のようなコードがあったとき、

package
{
    import flash.display.Sprite;
    public class FunctionTest extends Sprite
    {
        public function FunctionTest()
        {
        }
        
        
        private function onClick(e:Event=null):void
        {
            trace(this);
        }
        
        
        private function aboutMethodClosure():void
        {
            stage.addEventListener(MouseEvent.CLICK, onClick);
            
            var o:Object = {};
            o.onClick = onClick;
            o.onClick();
        }
        
        
        private function aboutFunctionClosure():void
        {
            var f:Function = function():void
                             {
                                trace(this);
                                trace(this is Function);
                             };
            f();
            var o:Object = {};
            o.f = f;
            o.f();
        }
    }
}

各関数は以下のように分類できます。

コンストラクタ

FunctionTestは通常コンストラクタといいますが、正確にはコンストラクタメソッドといってメソッドの一部です。

メソッドクロージャ

コード中のメソッド’aboutMethodClosure’の中で、
Stageのリスナとして’onClick’メソッドを渡しています。
このとき、onClickはクラスから抽出されています。
これがメソッドクロージャ。

また、その後にObjectインスタンスを生成して
そのインスタンスにonClickというメンバを定義し、onClickメソッドを代入しています。
ここで代入したonClickメソッドもクラスから抽出されたメソッドなのでメソッドクロージャ。

関数クロージャ

関数クロージャは、メソッド以外の全ての関数ですが、
ほとんどの場合メソッド内で新しく定義された関数が関数クロージャになります。
関数内で新しく定義された関数であることから「ネストされた関数」という言い方もするようです。

この例では匿名関数を生成し、変数’f’に代入していますが、以下のような書き方も出来ます。

package
{
    class myClass
    {
        private function myFuncA() : void
        {
            function myFuncB() : void
            {
            }
        }
    }
}

この場合、
myFuncAはメソッド、
myFuncBは関数クロージャ。

メソッドクロージャと関数クロージャの違い

メソッドクロージャ’onClick’と、関数クロージャ’f’は
実行されるとどちらもtrace(this)します。
メソッドクロージャと関数クロージャでは、このthisが指す参照が大きく違います。

実行結果は以下のようになります。

//メソッドクロージャ
[object FunctionTest]
[object FunctionTest]
//関数クロージャ
[object global]
[object Object]

メソッドクロージャの方は、どのような呼び出し方をされても
thisの参照先は「そのメソッドが定義されたインスタンス」を、
つまりFunctionTestインスタンスを指します。
このようにthisの参照先が常に束縛されているので「バインドメソッド」とも呼ぶようです。

それに対して、関数クロージャは
呼び出し方によって参照先が変わります。
最初のf()での呼び出しですが、このfはどのオブジェクトの持ち物でもありません。
が、このときアクティベーションオブジェクトと呼ばれるObjectが内部的に生成されていて、
fはこのアクティベーションオブジェクトのスコープで実行されます。

thisの参照を解決するためにスコープチェーンをたどりこのアクティベーションオブジェクトに到達し、
thisの参照先がアクティベーションオブジェクトになっています。
(ここの理解は自信なし。スコープチェーンをたどってGlobalに達しているのかもしれん)

(thisの値はスコープチェーンと関係なかった。たんにこの関数が誰に関連づけられているかでthisが指すものは決まる。誰の持ち物でもない場合はglobalオブジェクトが返る)
それに対して、次のo.f()の呼び出しでは、このfはoに含まれた関数なので
thisはObjectインスタンス’o’を指しています。

AS2の頃のクロージャ

AS2では「メソッドクロージャ」はなく、全て「関数クロージャ」でした。
なので、以下のようにイベントのリスナに登録すると
クロージャはMouseのスコープで実行されてしまい、thisはMouseを指していました。

class MyClass
{
    function MyClass()
    {
        Mouse.addListener(onClick);
    }
    private function onClick():void
    {
        trace(this);
    }
}

これを回避するために、Delegate.create()していたのはいい思い出ですよね。
AS3でのメソッドクロージャの導入は、この面倒を解決するためのものだと勝手に想像しています。


次回のエントリーへつづく

カテゴリー: ActionScript3 パーマリンク

AS3のクロージャ への2件のフィードバック

  1. [IT]ActionScript3.0での匿名関数内におけるthisの扱い

    Flexでシステム開発してて完全にはまった件について。 匿名関数の中でインスタンス変数を操作する際、thisキーワードをつけたら想像した通りに動かない。…

  2. xingxx のコメント:

    関数とメソッドの違い[ActionScript 3.0]

    プログラミング経験者なら誰もが一度は思ったことがあると思うこの問題。 言語によっ…

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です