Wonderflでthisとglobalについて質問してみた
こんにちは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); })(); } } }
結果は・・・
ここで気になるのは、無名関数の中のthisは[object global], 外側では[object Main]となっていて両方違うのに、numChildrenは内側でも外側でも同じということ.
クロージャーの外側ではthisはクラスのインスタンスそのものなので、
this.numChildrenとnumChildrenは同じで、this.なんて付けてもつけなくても同じじゃないか?なんて思ってしまいます.
imajukさんより頂いた説明や、ブログの記事を元に自分なりにまとめてみました。
まず、クラスや関数はスコープといって、変数・メソッドの定義を探す範囲を作ります。例えば、上のコードのスコープを図にすると、下のようになります。四角は一つのスコープに対応します。

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!
こんなコード、嫌ですね。では、またー!!