『納得』したい、関数の実行。

フォーラムで質問して、ようやく理解できてきました。
プロパティの参照がメソッドの実行方法によって出来ない理由


しかしこういった場で質問するとすごく緊張します...。
何とか理解しようと必死になっている自分がいます。
あと、僕はところどころ要点とずれた返信をしている気がしてきまして、
努力もむなしくまだまだ文章の書き方や応答の仕方の勉強が足りていないように感じました。


話がずれましたが、今回の要点は「関数の実行」です。
普段適当に呼び出していた関数というものには何か秘密があるようです。
それをまとめておこうと思います。
(今回はメソッドと言わず関数と言うことにします。)

1. 関数内でメンバを参照できない?

あるとき僕が関数を参照しているプロパティ「_func」を取得する関数「getFunction()」を作り、
その「getFunction()」を介して「_func」が参照している関数を実行しようとした際、
なぜか作ったクラスのメンバを参照できないという事態に陥りました。
そのクラスは以下のとおりです。

class TestClass
{
    private var _func:Function;
    private var _testVar:Number;
    
    // コンストラクタ
    public function TestClass()
    {
        _func = testFunction;
        _testVar = 0;
        
        _func();          // 出力:0
        getFunction()();  // 出力:undefined ←予想していた出力は0。なぜ?
    }
    
    // テストメソッド
    private function testFunction():Void
    {
        trace(_testVar);
    }
    
    // メソッドを割り当てる変数取得メソッド
    public function getFunction():Function
    {
        return _func;
    }
}

僕の今までの経験だと、「_func」とそれをを返す「getFunction()」は
同一のものと考えることができると思っていました。
ところが「_func()」と「getFunction()()」の動作結果は異なります。
ど う い う こ と だ ッ!


# これ以下は少々僕の主観が混ざりますのでご注意下さい。

2. 関数内での「this」

関数は呼び出し方によって内部の「this参照」が変るようです。

野中文雄さんは書きました:
オブジェクト(this)を参照(ターゲットと)してメソッドを呼出せば、functionブロック内でthisが参照できます。

つまり1.の不具合の原因は、「getFunction()」は関数「だけ」を返すので、
どのオブジェクトを参照して関数を呼び出しているのかが分からないことが原因とのことです。
(「this._func」の「_func」だけを返すと表現すればいい...?)
試しに1.のコードの「testFunction()」を

private function testFunction():Void
{
    trace([this, _testVar]);
}

と書き換えてみますと、出力が

[object Object],0
undefined,undefined

となります。
つまり「getFunction()()」で呼び出した場合、
「this」が「undefined」となるのです。
「this」が不明なら「this._testVar」が「undefined」になるのは当然です。
無事実行できている「_func()」は「this._func()」と同じなので、
実行した関数内の「this」は「TestClass自身」を指し「this._testVar」を参照できます。

3. 解決方法

じゃあ「getFunction()()」で関数を実行でけへんの?
と思われるかもしれませんが、実行する方法はあります。
「getFunction()()」を実行する際、
曖昧だった「this」となるオブジェクトを指定してあげればいいのです。
そのための関数が

Function.call()

です。
「getFunction()()」を「getFunction().call(this)」に書き換えればいいのです。
これで「this._func()」と同じように
オブジェクトを参照して関数を呼び出すことができます。
実際に実行してみますと

[object Object],0
[object Object],0

となり、正常に「_testVar」を参照できていることが確認できます。


そして方法はもう一つあります。 # ここは特に解釈が曖昧なので注意!
1.のコードの「getFunction()」を

public function getFunction():Function
{
    return mx.utils.Delegate.create(this, _func);
}

と書き換えると、「getFunction()()」でも正常に「_testVar」を参照できます。
(「mx.utils.」はパッケージなので、事前にimportしておいても良い。)
ここで出てきた

Delegate.create()

は、恐らく「this」と「_func」を繋げて(表現が思いつかない。)返すのだと思います。
つまり「this._func」をセットで返し「getFunction()()」が「this._func()」と同じということで
正常に「_testVar」を参照できるのだと思います。
(ちなみにFlashのヘルプで「Delegate.create()」を調べてみたけど説明の意味が理解不能ッ!)

4. 応用

以上のような関数の特性を利用すると、
「this」の参照をコントロールすることで処理を分岐する方法を思いつきました。

// タイムライン: _root
// フレームアクション
function xTest():Void {
    trace(this);
    this.comFunction();
}

function xGetFunction():Function {
    return xTest;
}

function comFunction():Void
{
    trace([this, "_root.comFunction()"]);
}

xTest();           // 出力: _level0と_level0,_root.comFunction()
xGetFunction()();  // 出力: undefinedのみ

var obj:Object = new Object();
obj.myFunction = xGetFunction();
obj.toString = function():String  {
    return "this is obj";
};

obj.comFunction = function():Void
{
    trace([this, "obj.comFunction()"]);
};

obj.myFunction();          // 出力: this is objとthis is obj,obj.comFunction()
xGetFunction().call(obj);  // 出力: this is objとthis is obj,obj.comFunction()

オブジェクトは違いますが、同名の関数を定義しておくことで、
「this」の参照をコントロールすれば任意の関数を実行できるようです。
コレは使える!
・・・と思いたいところですが、特に使い道を思いつきません。

あとがき

超長くなりました。
しかしまた引っかかりそうな仕掛けを理解できたので
安心して制作を進めることができそうです。
しかし個人的レポートに時間をかけすぎです。
当分無理です。




あー以上。