こんにちはtaro(@9re)です.

ひょんなことからthisってなんだろうって思い始めて、良くよく考えると分かっていないなーなんて思ったので、

思い切ってWonderflで質問してみました.

 コードはこんな感じ

package {
    import flash.display.Sprite;
    import com.bit101.components.PushButton;
    import com.actionscriptbible.Example;
    
    public class Main extends Example {
        public function Main() {
            var _this:Sprite = this;
            trace("[outside]this: " + this);
            trace("[outside]numChildren: " + numChildren);
            trace("[outside]this.numChildren: " + this.numChildren); 
            
            (function ():void {
                trace("[inside]this: " + this);
                trace("[inside]numChildren: " + numChildren);
                trace("[inside]this.numChildren: " + this.numChildren);
                trace(_this.removeChildAt === removeChildAt);
                trace(this.removeChildAt === removeChildAt);
            })();
        }
    }
}

結果は・・・

How Do You Explain 'this'? - wonderfl build flash online

ここで気になるのは、無名関数の中のthisは[object global], 外側では[object Main]となっていて両方違うのに、numChildrenは内側でも外側でも同じということ.

クロージャーの外側ではthisはクラスのインスタンスそのものなので、
this.numChildrenとnumChildrenは同じで、this.なんて付けてもつけなくても同じじゃないか?なんて思ってしまいます.

imajukさんより頂いた説明や、ブログの記事を元に自分なりにまとめてみました。

まず、クラスや関数はスコープといって、変数・メソッドの定義を探す範囲を作ります。例えば、上のコードのスコープを図にすると、下のようになります。四角は一つのスコープに対応します。

scopes.png

ActionScriptでは、numChildrenというような名前(forとかvarとかActionScript構文を構成するもの以外)に出くわしたとき、その定義が何であるかを探します。

まず、最初にその名前が登場するスコープを探し、見つからなければ一つ外側のスコープを探します。

globalというスコープは特殊で、一番外側にあるスコープです。無名関数を定義すると、その中のスコープはglobalとなります。

上のコードでは、無名関数の中でnumChildrenやremoveChildAtを参照していますが、これらは、globalの中にはないので、ひとつ上、function Main()(コンストラクタ)を探し、さらにそこでも定義していないので、class Mainの中を探しに行き、そこで定義が見つかります。(さらに見つからない場合は、未定義の変数・メソッドということになります。)

ということで、thisが無名関数の中と外で違うにも関わらず、numChildrenやremoveChildAt等が同じものを示すということになります。

なるほど!だんだんglobalって何だろうと僕は思ってきました・・・ということでglobalについて実験をしてみようと思いました!(余計)

globalについて実験をしてみる

検証1: globalは同じか?

まず、気になるのは二つの無名関数のglobalは同じか?検証コードは簡単ですね。

var global_a:* = (function ():Object {
  return this;
})();

var global_b:* = (function ():Object {
  return this;
})();

trace(global_a, global_a === global_b); // [object global] true

意外にも?!同じです。ということは勿論、

(function ():void {
    this.say = trace;
})();

(function ():void {
    this.say("hello, global!"); // hello, global!
})();

なんだか少し気持ち悪いですが、勿論動きます。先程、globalを最も外側にあるスコープだと説明しました。今globalにメソッドが追加できたということは、this.って要らなくない?と思います。

しかし、そうは許してくれないのは、コンパイラです。

Call to a possibly undefined method say

なんて言ってくるでしょう。そんなことではめげません。コンパイルさえ通ればきっとこっちのもの(何が?)なのでやってみましょう。

withによるハック

with(obj)はコード・ブロックのスコープをobjにします。コンパイラはobjの中身を確認しないので、これにより、コンパイルが通ります。

(function ():void {
    this.say = trace;
})();

(function ():void {
    with (1) say("hello, global!"); // hello, global!
})();

検証2: globalのスコープはクラスの外側にちゃんとあるのか?

withによるハックをしたので、もう簡単ですね。スコープから新たに追加されたメソッドを探し出すか調べてみます。

(function ():void {
    this.say = trace;
})();

with (1) say("hello, global!"); // hello, global!

勿論sayなんてメソッドはこのクラスのものではありません。なので、sayという名前をクラスの外のスコープに探しに行きます。

globalまでスコープを探しに行けば、sayは見つかるのですが、エラーが起きずにglobalのsayが呼ばれているので、globalのスコープはクラスより外側にあります。

withハック以外の方法

上のwithハックはwith自体がスコープをどうかえているかなんて分からないし、無効だとおっしゃられる方がいるかも知れません。そこで・・・

Flash IDEをお使いの方は、パブリッシュの設定 > ActionScript3.0の詳細設定からエラーのStrictモードのチェックを外します。

flexコンパイラに対しては、コンパイラ・オプションの指定でコンパイラを黙らせることができそうです。

<flex-config>
  <compiler>
    <strict>false</strict>
  </compiler>
</flex-config>
(function ():void {
    this.say = trace;
})();

say("hello, global!"); // hello, global!

こんなコード、嫌ですね。では、またー!!

HTML5飯