したがって、例で説明しているのは、プロキシパターンのデモンストレーションではなく、「オブジェクトの呼び出し」とJavaScriptでの動作に関する混乱のデモンストレーションです。
JavaScriptでは、関数は「ファーストクラス」です。これは本質的に、関数が他のデータと同じようにデータであることを意味します。それでは、次の状況を考えてみましょう。
var fn = (function () { return this.x; }),
a = {
x : 1,
fn : fn,
},
x = 2,
nothing = (function (z) { return z; });
aしたがって、 2つのプロパティを持つオブジェクトがあります:fnとx。また、変数x、 (これは)を返すfn関数であり、 (これは渡されたものをすべて返します)。this.xnothing
評価するa.xと、が得られ1ます。評価するxと、が得られ2ます。とても簡単ですね ここで、を評価するnothing(a.x)と、が得られ1ます。それもとても簡単です。1ただし、プロパティに関連付けられている値a.xがオブジェクトに関連付けられていないことを理解することが重要aです。独立して存在し、値として簡単に渡すことができます。
JavaScriptでは、関数は同じように機能します。プロパティである関数(「メソッド」と呼ばれることが多い)は、単純な参照として渡すことができます。ただし、そうすることで、オブジェクトから切断される可能性があります。thisこれは、関数内でキーワードを使用するときに重要になります。
キーワードはthis「呼び出し元オブジェクト」を参照します。これは、関数が評価されるときに関数に関連付けられるオブジェクトです。関数の呼び出し元オブジェクトを設定するには、次の3つの基本的な方法があります。
- ドット演算子(例)を使用して関数を呼び出すと
a.fn()、関連するオブジェクト(例ではa)が呼び出し元オブジェクトとして設定されます。
- 関数
callまたはapplyプロパティを使用して関数を呼び出す場合は、呼び出し元のオブジェクトを明示的に設定できます(これがなぜ役立つのかはすぐにわかります)。
- メソッド1またはメソッド2で呼び出し元オブジェクトが設定されていない場合は、グローバルオブジェクトが使用されます(ブラウザーでは、これは通常呼び出され
windowます)。
では、コードに戻りましょう。を呼び出すとa.fn()、と評価され1ます。ドット演算子を使用しているためthis、関数内のキーワードがに設定されるため、これは予想されることです。aただし、単純にを呼び出すと、グローバルオブジェクトのプロパティを参照しているため(グローバルが使用されていることを意味します) 、fn()戻ります。2xx
さて、ここで物事がトリッキーになります。あなたが電話した場合はどうなりますnothing(a.fn)()か?結果がに驚くかもしれません2。これは、に渡すとa.fnへnothing()の参照が渡されます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)。不要なので削除しました。