1

私はJavaScriptプロキシパターンを研究していますが、それから利益を得ることができる場所をまだ取得していません。したがって、2つの例を示し、それらの違いを指摘していただきたいと思います。

以下のコードをご覧ください。

  • addEventListener2つの呼び出しの違いは何ですか?handleDropそれらの1つは通常の方法で呼び出します。もう1つはプロキシパターンを使用します。
  • プロキシパターンアプローチを使用すると何が得られますか?

両方の関数をテストしましたが、どちらもhandleDrop正常に呼び出しました。

DndUpload.prototype.buildDropZone = function ()
{
    var self = this,

    this.dropZone.addEventListener('drop', function (e) { self.handleDrop.call(self, e) }, false);
    this.dropZone.addEventListener('drop', self.handleDrop, false);


    DndUpload.prototype.handleDrop = function (e)
    {
        alert("test");
        ...
    };
}

JavaScriptのプロキシパターンの非常に明確な説明を含む良いリファレンスを私に提供することができます。

4

3 に答える 3

10

したがって、例で説明しているのは、プロキシパターンのデモンストレーションではなく、「オブジェクトの呼び出し」とJavaScriptでの動作に関する混乱のデモンストレーションです。

JavaScriptでは、関数は「ファーストクラス」です。これは本質的に、関数が他のデータと同じようにデータであることを意味します。それでは、次の状況を考えてみましょう。

var fn = (function () { return this.x; }),
    a = {
        x : 1,
        fn : fn,
    },
    x = 2,
    nothing = (function (z) { return z; });

aしたがって、 2つのプロパティを持つオブジェクトがあります:fnx。また、変数x、 (これは)を返すfn関数であり、 (これは渡されたものをすべて返します)。this.xnothing

評価するa.xと、が得られ1ます。評価するxと、が得られ2ます。とても簡単ですね ここで、を評価するnothing(a.x)と、が得られ1ます。それもとても簡単です。1ただし、プロパティに関連付けられている値a.xがオブジェクトに関連付けられていないことを理解することが重要aです。独立して存在し、値として簡単に渡すことができます。

JavaScriptでは、関数は同じように機能します。プロパティである関数(「メソッド」と呼ばれることが多い)は、単純な参照として渡すことができます。ただし、そうすることで、オブジェクトから切断される可能性があります。thisこれは、関数内でキーワードを使用するときに重要になります。

キーワードはthis「呼び出し元オブジェクト」を参照します。これは、関数が評価されるときに関数に関連付けられるオブジェクトです。関数の呼び出し元オブジェクトを設定するには、次の3つの基本的な方法があります。

  1. ドット演算子(例)を使用して関数を呼び出すとa.fn()、関連するオブジェクト(例ではa)が呼び出し元オブジェクトとして設定されます。
  2. 関数callまたはapplyプロパティを使用して関数を呼び出す場合は、呼び出し元のオブジェクトを明示的に設定できます(これがなぜ役立つのかはすぐにわかります)。
  3. メソッド1またはメソッド2で呼び出し元オブジェクトが設定されていない場合は、グローバルオブジェクトが使用されます(ブラウザーでは、これは通常呼び出されwindowます)。

では、コードに戻りましょう。を呼び出すとa.fn()、と評価され1ます。ドット演算子を使用しているためthis、関数内のキーワードがに設定されるため、これは予想されることです。aただし、単純にを呼び出すと、グローバルオブジェクトのプロパティを参照しているため(グローバルが使用されていることを意味します) 、fn()戻ります。2xx

さて、ここで物事がトリッキーになります。あなたが電話した場合はどうなりますnothing(a.fn)()か?結果がに驚くかもしれません2。これは、に渡すとa.fnnothing()の参照が渡されますfnが、呼び出し元のオブジェクトは保持されないためです。

これは、コーディング例で行われていることと同じ概念です。関数でキーワードhandleDropを使用する場合、使用thisするハンドラーフォームに応じて値が異なることがわかります。これは、2番目の例では、への参照を渡しているためですが、handleDropこのnothing(a.fn)()例と同様に、呼び出されるまでに、呼び出し元のオブジェクト参照は失われます。

それでは、パズルに何か他のものを追加しましょう:

var b = {
    x : 3
};

bプロパティがありますが(したがって、の使用のx要件を満たします)、を参照するプロパティがないことに注意してください。したがって、に設定された関数を呼び出したい場合は、に新しいプロパティを追加する必要があるように思われるかもしれません。ただし、代わりに、前述のメソッドを使用して、呼び出し元オブジェクトとして明示的に設定できます。fnthisfnfnthisbbapplyfnb

fn.apply(b); //is 3

これは、新しい関数「ラッパー」を作成することにより、呼び出し元のオブジェクトを関数に「永続的に」バインドするために使用できます。これは実際には永続的にバインドされているわけではなく、目的の呼び出し元オブジェクトで古い関数を呼び出す新しい関数を作成しているだけです。このようなツールは、多くの場合、次のように記述されます。

Function.prototype.bind = function (obj) {
    var self = this;
    return function() { 
        return self.apply(obj, arguments);
    };
};

したがって、そのコードを実行した後、次のことができます。

nothing(a.fn.bind(a))(); //is 1.

トリッキーなことは何もありません。実際、このbind()プロパティはES5に組み込まれており、上記の単純なコードとほとんど同じように機能します。そして、私たちのbindコードは、実際には、もっと簡単にできることを行うための非常に複雑な方法です。aプロパティとして持っているのでfn、ドット演算子を使用して直接呼び出すことができます。callとの紛らわしい使用はすべてスキップできますapply。関数が呼び出されたときに、ドット演算子を使用して呼び出されることを確認する必要があります。上記でその方法を確認できますが、実際には、はるかに単純で直感的です。

nothing(function () { return a.fn(); })(); //is 1

データ参照をクロージャスコープに格納する方法、関数がファーストクラスオブジェクトである方法、および呼び出し元オブジェクトがどのように機能するかを理解すると、これらすべてが非常に理解しやすく、かなり直感的になります。

「プロキシ」に関しては、それらも同じ概念を利​​用して関数にフックします。a.fnそれで、あなたが呼び出された回数を数えたいとしましょう。これを行うには、次のようにプロキシを挿入します(bind上記のコードのいくつかの概念を利用します)。

var numCalls = (function () {
    var calls = 0, target = a.fn;

    a.fn = (function () {
         calls++;
         return target.apply(a, arguments);
    });

    return (function () {
        return calls;
    });
}());

したがって、を呼び出すたびに、 !の機能を実際に変更せずに呼び出されnumCalls()た回数が返されます。かなりかっこいいです。ただし、で参照される関数を変更したことを覚えておく必要があります。そのため、コードの最初を振り返ると、それはもはや同じではなく、互換的に使用できなくなっていることに気付くでしょう。しかし、その理由は今やかなり明白なはずです!a.fn()a.fna.fna.fnfn

それは基本的に数ページのテキストでの1週間のJavaScript教育であったことを私は知っていますが、それはそれが得るのと同じくらい簡単です。概念を理解すると、多くのJavaScriptパターンの機能、有用性、および能力を非常に簡単に理解できるようになります。

それが物事をより明確にしたことを願っています!

更新:私の不必要な使用を指摘してくれた@pimvdbに感謝します[].slice.call(arguments, 0)。不要なので削除しました。

于 2012-09-13T19:29:13.600 に答える
3

基本的に、self.handleDrop直接渡すことは、次の関数を渡すことと機能的に同等です。

function() {
  return self.handleDrop.apply(this, arguments);
}

すべてが元の関数に渡されるため:

  • this値_
  • 引数
  • 戻り値

これを念頭に置いて、次のように関数を比較します。

function(e) { self.handleDrop.call(self, e) }
function() { return self.handleDrop.apply(this, arguments); }

プロキシ方式との違いは次のとおりです。

  • 戻り値は渡されません。
  • すべての引数を通過させるわけではありません(最初の引数のみe
  • 値は渡されませんがthis、事前定義された値を使用しますself

addEventListenerさて、最初の2つの項目は、戻り値を気にしないため、ここでは違いはありません。また、とにかく1つの引数のみを渡します。

ただし、3番目の項目は重要ですthis。関数に異なる値を設定します。デフォルトでthisは、はイベントをバインドする要素です(ブラウザによって設定されます)。プロキシ方式を使用して、別のthis値を設定できます。

buildDropZoneさて、あなたのスニペットでは、呼び出されるたびにプロトタイプ関数を設定している理由が完全には明確ではありません。通常、プロトタイプ関数は1回だけ定義します。ただし、ハンドラーhandleDropがプロキシ方式を使用して呼び出される場合thisは、インスタンスを参照しDndUploadます。これは、一般にプロトタイプ関数と一致しています。

于 2012-09-13T18:36:12.227 に答える
2

以下のコードを検討してください。

function printThis() {
  console.log(this);
}

var someObject = {
  performTest : function() {
    var self = this;

    someOtherObject.higherOrderFunction(printThis);
    someOtherObject.higherOrderFunction(function(){printThis.call(self)});
  }
}

var someOtherObject = {
  higherOrderFunction : function(f) {
      f();
  }
}

何がsomeOtherObject.higherOrderFunction(printThis)返されますか?どうですかsomeOtherObject.higherOrderFunction(function(){printThis.call(self)})

最初の質問に対する答えは、someObject.performTest()を誰がどのように呼び出すかによって異なります。グローバルコンテキストからsomeObject.performTest()を呼び出すだけで、おそらく.が出力されますWindow

someObject2つ目は、何があっても常にインスタンスを出力します。

関数の実行コンテキストを正確に制御したい場合は、クロージャまたは「プロキシパターン」と呼ぶと便利です。

注:thisjavascriptでは、他の言語(Javaなど)のようには動作しません。

于 2012-09-13T18:46:50.703 に答える