その this は何なのか?
Tweener という便利なライブラリを使って Tween アニメーションを作っていたところ、エラーは起こらないのに動作がおかしい箇所を発見しました。
該当する箇所の共通点を探してみたところ、以下のような条件があてはまりました。
- イベントリスナーの登録時に、引数に無名関数を使っている
- 無名関数の中で this を使っている
同じような場合でも、イベントリスナーの引数に名前つきの関数を渡した場合や、 this ではなくオブジェクトの名前を指定した場合は正しく動作しました。それでは、この this の正体は何なのでしょうか?
コードを書いて、調べてみます。
stage.addEventListener(Event.ACTIVATE, doTrace); stage.addEventListener(Event.ACTIVATE, function(event:Event) {trace(this)}); function doTrace(event:Event):void { trace(this); }
このコードを実行して、トレースされたのがこれ。
[object MainTimeline] [object global]
上の方は私が想定した this であり、メインのタイムラインを指しています。では、 global とは?
上のコードを書き換えて、さらにチェック。
stage.addEventListener(Event.ACTIVATE, function(event:Event) {trace(describeType(this))});
出力はこうなりました。
<type name="global" base="Object" isDynamic="true" isFinal="true" isStatic="false"> <extendsClass type="Object"/> <method name="unescape" declaredBy="global" returnType="String"> <parameter index="1" type="String" optional="true"/> </method> <constant name="Class" type="Class"/> <constant name="Infinity" type="Number"/> <method name="encodeURI" declaredBy="global" returnType="String"> <parameter index="1" type="String" optional="true"/> </method> <constant name="Function" type="Function"/> <constant name="uint" type="uint"/> <method name="parseInt" declaredBy="global" returnType="Number"> <parameter index="1" type="String" optional="true"/> <parameter index="2" type="int" optional="true"/> </method> <constant name="int" type="int"/> <constant name="NaN" type="Number"/> <method name="isNaN" declaredBy="global" returnType="Boolean"> <parameter index="1" type="Number" optional="true"/> </method> <method name="decodeURIComponent" declaredBy="global" returnType="String"> <parameter index="1" type="String" optional="true"/> </method> <method name="escape" declaredBy="global" returnType="String"> <parameter index="1" type="String" optional="true"/> </method> <constant name="AS3" type="*"/> <constant name="undefined" type="*"/> <constant name="Boolean" type="Boolean"/> <method name="parseFloat" declaredBy="global" returnType="Number"> <parameter index="1" type="String" optional="true"/> </method> <constant name="String" type="String"/> <method name="decodeURI" declaredBy="global" returnType="String"> <parameter index="1" type="String" optional="true"/> </method> <method name="isFinite" declaredBy="global" returnType="Boolean"> <parameter index="1" type="Number" optional="true"/> </method> <constant name="Array" type="Array"/> <constant name="Object" type="Object"/> <constant name="Namespace" type="Namespace"/> <constant name="Number" type="Number"/> <method name="encodeURIComponent" declaredBy="global" returnType="String"> <parameter index="1" type="String" optional="true"/> </method> </type>
見る限り、これはグローバルオブジェクトを指しているようです。どうやら、無名関数のように特定のオブジェクトに属さない関数内では、 this の参照先はどこでもないため、グローバルオブジェクトに設定されてしまう様子。
ちなみに名前つきの関数の方はメインのタイムライン上に定義されているため、そこで this を使った場合は MainTimeline が this として設定されています。
さらに調べたところ、無名関数以外にもオブジェクトのプロパティとして関数を設定した場合にも同じような問題が発生するようです。
var myObj = new Object(); myObj.doTrace = function(event:Event):void {trace(this)}; stage.addEventListener(Event.ACTIVATE, myObj.doTrace);
動的に関数を定義するような場合、その関数内では this の参照先は global になってしまうというのが結論でしょうか。面倒なようでも、きちんと関数を定義した方が確実なケースが多いようです。