newについて考え直す - 無名コンストラクタ
どうもこんにちは!七夕ですね。_level0も新しくなりました。
皆さん何をお願いしましたか? 僕は良く分かんないけど、取り合えずモテてー!!ので、今回も function(){}周りの非モテ話を始めようと思います。
newの後に持ってくる
前回のエントリでfunction(){}の中のローカル変数を 保存することで、クラスのようなオブジェクトを作りました。実はnewの後に無名関数を持ってくることも出来ます。 ますますクラスっぽいですね。見てみましょう。
var these:Array = []; var func:Function = function () { trace(this); these.push(this); } var instance:Object = new func(); // [object Object] func(); // [object MainTimeline] trace(these[0] == instance, this == these[1]); // true true
フレームにこいつを貼り付けてFlashでムービーをプレビューすると//でコメントアウトしているものが 出力されます。thisが違いますね。newのあとの場合thisは普通に使われるコンストラクタ内のthisと同じですね。 一番最後のトレースの比較を見ると明らかです。 フレームスクリプトとドキュメントクラスに書いた場合は少しこれと様子が違いますが、大体同じです。 ちょっとサンプルを作ってみました。
上記サンプルの9行目で定義される関数を
private var myConstructor0:Function = function ():void { messages.push("\"this\" inside myConstructor: " + this.toString()); these.push(this); };
31行目でmyConstructor0();と実行これは勿論問題ありません。ところが、 33行目でnew myConstructor0();とすると、 messagesやtheseというのは、 この無名関数のスコープの中にありませんから、(つまりmyConstructor内のthisはこの newで生成されるインスタンスですから当然messagesやtheseというプロパティは持っていません) エラーが出ます。
private var constructorGenerator:Function = function ():Function { return function ():void { messages.push("\"this\" inside constructorGenerator: " + this.toString()); these.push(this); }; }
高階関数を一つはさむと、
var myInstance2:Object = new (constructorGenerator());
まずconstructorGenerator()実行時にmessages, theseがドキュメントクラスのスコープで評価されてからnewされるので、うえの様な 参照の問題は起きません。最後にクロージャーを使ってforEachのコールバックを作っています。
private function findThisObject($targetName:String, $target:Object):Function { var found:Boolean = false; return function($this:Object, $index:int, $array:Array):void { if ($target && $this && $target == $this) { messages.push($targetName + " found at index " + $index.toString()); found = true; } else if (!found && $index + 1 == $array.length) messages.push($targetName + " not found!"); } }
上にあるfoundを取り除いてelse ifの!foundを取り除くと何が起こるでしょうか?
these.forEach(findThisObject("myInstance1", myInstance1));
を実行した時に、$index = 2と$index + 1 = $array.lengthの両方で メッセージが出てしまいます。こういう風にフラグをクロージャーのスコープ内に設定して回避しています。 (for文とbreak文を二回書く方が圧倒的に良いですが、なんとなくクロージャーの例として作ってみました) この最後の例はとにかくやってみたって感じで良くないですが、速度や実行時のコストを気にしないのであれば、 高階関数やクロージャーを使ってワンクッションはさめば、ややこしい参照の問題が見やすくなります。 特にJSにより近いAS2なんかでのコーディングテクニックとして如何でしょうか?