アクティベーションオブジェクトとスコープチェーン

3回シリーズの2回目です

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

参照の解決

以下のようなコードがあるとします。

package
{
    public class MyClass extends Sprite
    {
         private function methodA():void
        {
        }
        
        private function methodB():void
        {
                    var f:Function = function()
                                    {
                                        trace(methodA);
                                    }
                    f();
        }
    }
}

methodBを実行すると、メソッド’methodA’が出力されます。

// output
// function Function() {}

ごく普通で当たり前の光景ですよね。
でも、よく考えると不思議です。
関数クロージャ(匿名関数)’f’はFunctionインスタンスで、MyClassインスタンスとは独立した別のオブジェクトです。
なぜ、関数クロージャ’f’はMyClassのprivateなメソッドにアクセスできるのでしょうか?

僕は、「methodAの前にthisが省略されていて、そのthisはMyClassインスタンスを指しているから」だと勝手に理解していたのですが、調べてみると全然違いました。

前回のエントリーで書いたように、
関数クロージャ’f’の中でtrace(this)すると、[object global]が出力されます。
つまり、仮にthisが省略されていたとしても、関数クロージャ’f’内でのthisはMyClassインスタンスを指しません。

実は、このmethodAはスコープチェーンによって解決されています。

スコープチェーン

僕は、つい頭の中で「スコープチューン」と読んでしまうのですが、「チェーン」です。鎖です。
参照を解決するオブジェクトが連なった鎖をイメージするとよいかと思います。

関数が実行されるたびにスコープチェーンが生成されます。

  1. まず、methodBが実行されるとき、MyClassインスタンスが持つ全てのプロパティが参照できるオブジェクトが生成され、methodBのスコープチェーンに格納されます。(多分。。)
  2. つぎに、methodBのなかで関数クロージャ’f’が実行されるときに、そのスコープチェーンが渡されます。(多分。。)
  3. そして、tarce(methodA)でmethodAを解決する際に、このスコープチェーンを検索します。
  4. 最後に、スコープチェーンにある、1で出来たオブジェクトが検索にヒットし、めでたくmethodAにアクセスすることができます。

    (この1〜4の行程は、十分な裏が取れず推測が混じっているので正しくないかもしれない

スコープチェーンが生成されるときに作られるオブジェクトについては、リファレンスによると

Second, a scope chain is created that contains an ordered list of objects that Flash Player checks for identifier declarations.
次に、”スコープチェーン” が作成され、そこには Flash Player によって識別子宣言の有無がチェックされるオブジェクトが列挙されたリストが含まれます。

とありますが、正直この説明は漠然としすぎていてよくわかりません。
が、AS3のスコープチェーンについてのこれ以上詳しいリソースを見つけられませんでした。

知りたいのは、その「 Flash Player によって識別子宣言の有無がチェックされるオブジェクト」がどういうルールで生成されるのかなんですが。。
詳細をご存知の方はコメントください!

アクティベーションオブジェクト

さて、さっきの説明でははしょったんですが、
関数の実行時には、毎回アクティベーションオブジェクトと呼ばれるオブジェクトが生成されて、関数のスコープチェーンの先頭に加えられます。

Any time a function begins execution, a number of objects and properties are created. First, a special object called an activation object is created that stores the parameters and any local variables or functions declared in the function body. You cannot access the activation object directly, because it is an internal mechanism.
関数が実行を開始するたびに、多数のオブジェクトおよびプロパティが作成されます。最初に、”アクティベーションオブジェクト” という特別なオブジェクトが作成され、そこには関数本体で宣言されるパラメータとローカル変数または関数が格納されます。アクティベーションオブジェクトは内部メカニズムであるため、そこに直接アクセスすることはできません。
(ActionScript 3.0 のプログラミング > ActionScript 言語とシンタックス > 関数 > 関数のスコープ)

また、関数クロージャが実行される場合は、関数クロージャを呼び出した親関数のアクティベーションオブジェクトが関数クロージャのアクティベーションオブジェクトの後に追加されます。メソッドクロージャの実行時にはこのアクティベーションオブジェクトの受け渡しは行われません。

For a nested function, the scope chain starts with its own activation object, followed by its parent function’s activation object.
ネストされた関数の場合は、スコープチェーンはそのアクティベーションオブジェクトから始まり、その後に親関数のアクティベーションオブジェクトが続きます。

ちょっと例を変えて、説明を試みてみます。

public class ScopeTest extends Sprite
{
    private var myNum:int;
    public function ScopeTest()
    {
        methodA();
    }
    private function methodA():void
    {
        var myNum:int = 2;
        methodB();
    }
    private function methodB():void
    {
        var myNum:int = 3;
        var f:Function = function():void
                    {
                        var myNum:int = 4;
                        trace(myNum);
                    };
        f();
    }
}

このコードではmyNumという識別子を持つ変数が4カ所で定義されています。

  • ScopeTestクラスのプロパティ
  • methodAのローカル変数
  • methodBのローカル変数
  • 関数クロージャ’f”のローカル変数

関数クロージャ内でこのmyNumをtraceしたときに出力される値は何でしょうか?

// output
// 4

では、関数クロージャのローカル変数であるmyNumをコメントアウトしてみます。

public class ScopeTest extends Sprite
{
    private var myNum:int;
    public function ScopeTest()
    {
        methodA();
    }
    private function methodA():void
    {
        var myNum:int = 2;
        methodB();
    }
    private function methodB():void
    {
        var myNum:int = 3;
        var f:Function = function():void
                    {
                        //var myNum:int = 4;
                        trace(myNum);
                    };
        f();
    }
}

こんどは出力結果は3になり、methodBのmyNumを参照するようになりました。

//output
//3

さらに、methodBのローカル変数であるmyNumをコメントアウトしてみます。

public class ScopeTest extends Sprite
{
    private var myNum:int;
    public function ScopeTest()
    {
        methodA();
    }
    private function methodA():void
    {
        var myNum:int = 2;
        methodB();
    }
    private function methodB():void
    {
        //var myNum:int = 3;
        var f:Function = function():void
                    {
                        //var myNum:int = 4;
                        trace(myNum);
                    };
        f();
    }
}

結果は以下の通りで、ScopeTestインスタンスのmyNumプロパティが参照されます。

// output
// 0

この結果から、methodAのローカル変数myNumにはアクセスしていないことがわかります。
関数クロージャ’f’はmethodAが見えていません。
つまり、「methodAのローカル変数myNumは、関数クロージャ’f’のスコープチェーンのなかには存在しない」ということができます。

スコープチェーンが作られていく様子を詳細に追いかけてみます。
注意するべきポイントは、「関数クロージャが実行されるときに行われるアクティベーションオブジェクトの受け渡し」です。(下のリストの中の9の後半です)

  1. methodAが実行されるときにアクティベーションオブジェクトが生成されます。
    このアクティベーションオブジェクトには、プロパティ’myNum’を持ち、methodAのローカル変数’myNum’の値が格納されます。
    便宜上、アクティベーションオブジェクトを@methodA{myNum:2}と表現することにします。

  2. 次にmethodAのスコープチェーンが生成されます。
    このスコープチェーンにはScopeTestインスタンスのprivateな変数であるmyNumの参照をもつオブジェクトが含まれます。
    便宜上、このオブジェクトを#ScopeTest{myNum:0}と表現することにします。

  3. 2で作成されたスコープチェーンの先頭にアクティベーションオブジェクトが追加されます。
    methodAのスコープチェーンは以下のような順番になっています。
    [@methodA{myNum:2} – #ScopeTest{myNum:0}]
    便宜上、スコープチェーンをこのように表現します。

  4. methodBが呼び出されます。
  5. methodBが実行されるときにアクティベーションオブジェクト @methodB{myNum:3, f:関数クロージャ} が生成されます。
    このアクティベーションオブジェクトはmethodBのローカル変数をプロパティとして持ちます。

  6. methodBのスコープチェーンが生成され、アクティベーションオブジェクトが先頭に追加されます。
    methodBのスコープチェーンは以下のような順番になっています。
    [@methodB{myNum:3, f:関数クロージャ} – #ScopeTest{myNum:0}]

  7. 関数クロージャ’f’が呼び出されます。
  8. 関数クロージャ’f’が実行されるときにアクティベーションオブジェクト @f{myNum:4} が生成されます。
    このアクティベーションオブジェクトは関数クロージャ’f’のローカル変数をプロパティとして持ちます。

  9. 関数クロージャ’f’のスコープチェーンが生成され、アクティベーションオブジェクトが先頭に追加されます。
    さらに、このとき、呼び出しもとの親関数のアクティベーションオブジェクト(5で作られた @methodB{myNum:3, f:関数クロージャ} ) がその後に追加されます。
    [@f{myNum:4} – @methodB{myNum:3, f:関数クロージャ} – #ScopeTest{myNum:0}]

[@f{myNum:4} – @methodB{myNum:3, f:関数クロージャ} – #ScopeTest{myNum:0}]
最終的に、これが、関数クロージャ’f’のスコープチェーンになります。

左が先頭のオブジェクトで、右にオブジェクトが続きます。
関数が識別子を解決する場合は、先頭のオブジェクトから探していきます。

今回の例では、myNumの検索は先頭のアクティベーションオブジェクトでヒットし、関数クロージャ’f’のmyNumが参照されます。myNumを1つづつコメントアウトすると、検索にヒットするオブジェクトがひとつずつ右に移動します。
これは先ほどの出力結果と一致し、methodAがスコープチェーンに含まれていないことも確認できます。


参考サイト:http://www2u.biglobe.ne.jp/~oz-07ams/prog/js-notes/scope.html


次回のエントリーに続く

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

アクティベーションオブジェクトとスコープチェーン への1件のフィードバック

  1. ピンバック: AS3のインライン展開機能まとめ | とんぶろ

コメントを残す

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