関数クロージャの中でthisを使ってはならない
そーいえば、前twitter上でFlasherたちが、メンバを呼ぶときにthis付ける派か付けない派で議論してたね。
私の場合、thisを付けいないとわかりにくくなる場合は付ける。
じゃあ、関数クロージャの中では、this付けてメンバ呼び出す?
いやいや、関数クロージャの中では、thisがどれを指すかは実行時に決まるので基本的にthisは使わないよ。
関数クロージャの中ではthisが変わってしまう
メソッドの内部ではthisを付けようが付けまいが、変数名が被ってない限り、動作は変わらない。
なので、thisは読みやすさを重視するために付けるようなもんだ。
しかし、これが関数クロージャとなると話は別だ。
下の例で何が起こるのかわかるだろうか?
package { import flash.display.Sprite; public class Main extends Sprite { public function Main():void { (function():void { this.f(); })(); } private function f():void { trace("f() called!"); } } }
きっと、「f() called!」って出力が出るんだよね!
残念ハズレ。
「thisにfっていう関数はねーよ!」というランタイムエラーが出る。
これはthisが指している対象が違うため。
じゃあthisって何を指しているの?
次の例で、traceで何が出力されるのか予想してみよう。
package { import flash.display.Sprite; public class Main extends Sprite { public function Main():void { trace("1 " + this); (function():void { trace("2 " + this); })(); var o:Object = new Object(); o.f = function():void { trace("3 " + this); } o.f(); } } }
答え
1 [object Main]
2 [object global]
3 [object Object]
1はメソッド内に書かれているので、ふつーにMainのオブジェクトを指している。
2,3が関数クロージャ内でthisが使われているので、挙動が違う。
2がglobalというよくわからない場所を指していて、
3が、oを指している。
thisが変わる理由
thisが指している対象が変わってしまうのかは、一応理由がある。
ASも昔はJavaScriptのようなプロトタイプベースのオブジェクト指向言語だった。
それが、AS2.0、AS3.0になり、クラスベースのオブジェクト指向プログラミング(OOP)もサポートするようになった。
メソッドクロージャの内部でthisが変わるのは、そのプロトタイプベースのOOPの名残とも言える機能だ。
なので、AS3でもプロトタイプベースのOOPをやろうと思ったら出来なくはないが、
クラスベースのOOPの方が優れているので誰もやらない。
なので、この関数クロージャの内部でthisが変わるというのは、クラスベースのOOPをしている限り、
うっとうしいだけであり、邪魔で、意味のない機能だ。
AS3で誰もプロトタイプベースのOOPをしてないんだから、この機能は消えるべきだと思う。
であるからして、AS3は基本的にメソッドクロージャ内部ではthisを付けないこと推奨する。
ちなみにAS2までは、メソッドもthisの対象が変わってしまうというやっかいな問題があった。
Delegate.create()という関数を使ってその問題に対処していた。
それでもthisを使いたいです!
それでも、ローカル変数とメンバ名が被った場合など、thisを使いたい場合があるだろう。
何通りか対処の方法はある。
一番簡単で一般的な方法が、thisを関数クロージャの外で変数に入れておく方法。
package { import flash.display.Sprite; public class Main extends Sprite { public function Main():void { var f:Function = function():void { trace("local f() called!"); } var self:Main = this; (function():void { f(); self.f(); })(); } private function f():void { trace("member f() called!"); } } }
上の例ではselfという変数を使って代わりに使用している。
これなら、指している対象が変わってしまうということにはならない。
一安心だ。
他にもFunction#call()とかを使えば出来るけど、あんまり使わないので説明は省略。