関数クロージャの中で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()とかを使えば出来るけど、あんまり使わないので説明は省略。