アクションスクリプトの関数コンテキスト

匿名関数を使用したり、参照渡しで関数を渡したり、別の関数の本体で関数を直接宣言したりするのが好きです。 これは便利で実用的ですが、これらのメカニズムにはいくつかの問題が生じる可能性があります。 バージョン9以降、 Flash Playerは親関数をthis保存します。 簡単に聞こえますが、誰もがそれが何を意味するのか、それがなければ以前はどれほど難しかったのか理解できますか?

たとえば、次のような関数を記述することができます。

 public class TestClass { var property : Number; function updateValue(value : Number) : void { TestClass(this).property = value; } } 

そしてどこにでも転送します:

 var func : Function = new TestClass().updateValue; func(555); 

確かに、これで呼び出される場所には必ずTestClassクラスのインスタンスがあります。 しかし、私はこれについて話しているのではなく、 Action Scriptの関数で実行できるより興味深いアクションがあります。それらを検討します。

非同期呼び出し


ほとんどの場合、サーバー上のリモートメソッドを呼び出し、この呼び出しの結果を処理するタスクに直面していました。 サービスクラスがあるとしましょう ServerService ServerServiceは、応答を処理する関数へのコンストラクター参照を受け取り、ソースオブジェクトのプロパティを更新する一般的なタスクを実行します。

 class Example { function updateItem(item : SomeObject) : void { _tempObject = item; new ServerService(onGetResult).getResult(item.startValue); } function onGetResult(result : Object) : void { _tempObject.endValue = result; } private var _tempObject : SomeObject; } 

すべてが正しく書かれていますが、なぜそんなに複雑なのですか? スマートコードでこのナンセンスを単純化しましょう:

 function updateItem(item : SomeObject) : void { new ServerService(onGetResult).getResult(item.startValue); function onGetResult(result : Object) : void { item.endValue = result; } } 

この場合、 onGetResult関数は、 onGetResult関数のすべての変数とそのitem引数にアクセスできます。 多くの場合、この手法はコードの量を減らし、非同期性の否定的な意味合いを取り除くことができます。 ところで、 this関数ではonGetResultExampleインスタンスではなく、単にglobalます。

複数の非同期呼び出し


さらに興味深いのは、いくつかの非同期要求をループで作成し、それに応じて各応答を処理する必要がある場合です。次に例を示します。

 function updateItems(items : ArrayCollection) : void { for each (var item : SomeObject in items) { new ServerService(onGetResult).getResult(item.startValue); } function onGetResult(result : Object) : void { item.endValue = result; } } 

このコードでは、望ましい結果が得られません。 その時点で、サーバーが回答を返すと、 item変数はitemsコレクションの最後の要素を参照し、すべてのデータはそれだけに割り当てられます。 そのような状況では、関数の保存されたコンテキストも親変数のスコープも役立たず、何か他のものが必要です。

多くの場合、いわゆるLoader使用できます。

 function updateItems(items : ArrayCollection) : void { for each (var item : SomeObject in items) { new ValueLoader(item); } } class ValueLoader { public function ValueLoader(item : SomeObject) { new ServerService(onGetResult).getResult(item.startValue); function onGetResult(result : Object) : void { item.endValue = result; } } } 

関数のコンテキストは、サーバーが応答した後にitemを保存して更新するのに十分ではないため、関数のラッパーを作成します-コンテキストで必要なすべてを記憶できるクラスです。 クラスコンストラクターはまだ同じ関数であるため、 item引数は問題なくonGetResult関数で使用できます。

標準化されたContextFunction


最終的に、すべての種類のローダーを大量に作成したくない場合は、ユニバーサルタイプ(繰り返し使用するパターン)を導入できます。

 class ContextFunction { public function ContextFunction(targetFunction : Function, ... args) { _contextArgumnets = args; _targetFunction = targetFunction; } public function func(... args) : void { var targetArguments : Array = args.concat(_contextArgumnets); _targetFunction.apply(this, targetArguments); } private var _contextArgumnets : Array; private var _targetFunction : Function; } 

ソリューションの本質は、 ContextFunctionインスタンスContextFunction 、特定のロジックを持つ関数への参照と、誰かが呼び出したときに関数が受け取る未定義の引数のセットによって決定されることです。 また、呼び出し元のエンティティの要求に応じて、これらの引数に何か他のものが追加されます。 明確にするための例を考えてみましょう。

 function updateItems(items : ArrayCollection) : void { for each (var item : SomeObject in items) { new ServerService(new ContextFunction(onGetResult, item).func). getResult(item.startValue); } } function onGetResult(result : Object, item : SomeObject) : void { item.endValue = result; } 

これは本質的にはLoader場合と同じ解決策であり、より普遍的なものです。 ContextFunctionインスタンスはonGetResult保存しonGetResult 。これは、サーバーからの応答と、サーバー値が要求されたitemへのリンクを受け取ります。 つまり、一般に関数のコンテキストを放棄し、補助クラスのインスタンスを使用して目的の値を保存します。

結論として、これらのトリックはすべて私が実際に非常に頻繁かつ効果的に使用していることを保証できます。 これは指の問題ではありません。

Source: https://habr.com/ru/post/J52458/


All Articles