コンテキスト(this)をjavascriptの関数および関数の部分適用にバインドする

以前の投稿で、 javascriptのこれはオブジェクトにバインドされておらず、呼び出しのコンテキストに依存することを説明しました。 実際には、 これは常に関数内の特定のオブジェクトを常に参照する必要が生じます。

この記事では、この問題を解決する2つのアプローチを検討します。

1. jQuery.proxy-人気のあるjQueryライブラリを使用したアプローチ

2. Function.prototype.bind -JavaScript 1.8.5で追加されたアプローチ。 また、カリー化(関数の部分的な適用)への適用と、ユニットが認識している作業の微妙な点についても考えてみましょう。





はじめに



プロパティxとメソッドthisをコンソールに表示するメソッドfを含む単純なオブジェクトを考えます

var object = { x: 3, f: function() { console.log(this.x); } }
      
      





前の投稿で指摘したように、 object.f()を呼び出すと、コンソールに数字3が表示されます。この数字を1秒で印刷する必要があるとします。

 setTimeout(object.f, 1000); //  undefined //    —    : setTimeout(function() { object.f(); }, 1000); //  3
      
      





ラッパー関数を使用するたびに不便です。 関数object.f内のthisが常にオブジェクトを 参照するように、関数のコンテキストをバインドする方法が必要です



1. jQuery.proxy



 jQuery.proxy(function, context); jQuery.proxy(context, name);
      
      





jQueryが非常に人気のあるjavascriptライブラリであることは秘密ではないため、最初にjQuery.proxyを使用してコンテキストを関数にバインドすることを検討します。

jQuery.proxyは、呼び出されると、コンテキストcontextで元の関数functionを呼び出す新しい関数を返します 。 jQuery.proxyを使用すると、上記の問題は次のように解決できます。

 setTimeout($.proxy(object.f, object), 1000); //  3
      
      





同じコールバックを数回指定する必要がある場合、複製の代わりに

 setTimeout($.proxy(object.f, object), 1000); setTimeout($.proxy(object.f, object), 2000); setTimeout($.proxy(object.f, object), 3000);
      
      





$ .proxyの結果を別の変数に入れる方が良い

 var fn = $.proxy(object.f, object); setTimeout(fn, 1000); setTimeout(fn, 2000); setTimeout(fn, 3000);
      
      







ここで、 $ .proxy内でオブジェクトを2回指定したことに注意してみましょう(オブジェクトのメソッドが最初にobject.fである場合、2番目は送信されたコンテキストがobjectである )。 たぶん、重複を避ける機会がありますか? 答えはイエスです。 そのような場合、パラメーターを渡すための代替オプションが$ .proxyに追加されました。最初のパラメーターはオブジェクトで、2番目はそのメソッドの名前です。 例:

 var fn = $.proxy(object, "f"); setTimeout(fn, 1000);
      
      





メソッド名は文字列として渡されることに注意してください。



2. Function.prototype.bind



 func.bind(context[, arg1[, arg2[, ...]]])
      
      







Function.prototype.bindに移りましょう。 このメソッドはJavaScript 1.8.5で追加されました。

ブラウザの互換性
Firefox(Gecko):4.0(2)

クローム:7

Internet Explorer:9

オペラ:11.60

Safari:5.1.4



Mozilla Developer NetworkのエミュレーションFunction.prototype.bind
 Function.prototype.bind = function (oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 internal IsCallable function throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () {}, fBound = function () { return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; };
      
      







Function.prototype.bindには2つの目的があります-関数へのコンテキストの静的バインドと、関数の部分適用です。

基本的に、バインドはコンテキストコンテキストでfuncを呼び出す新しい関数を作成します。 引数arg1、arg2 ...が指定されている場合、それらは新しい関数への各呼び出しに追加され、新しい関数が呼び出されたときに指定されたものに直面します。



2.1。 コンテキストバインディング


バインドを使用してコンテキストをバインドするのは非常に簡単です。例を考えてみましょう。

 function f() { console.log(this.x); } var bound = f.bind({x: 3}); // bound -   - "",   this    {x:3} bound();//  3
      
      







したがって、導入部の例は次のように記述できます。

 var object = { x: 3, f: function() { console.log(this.x); } } setTimeout(object.f.bind(object), 1000); //  3
      
      







2.2。 関数の部分的な使用


簡単にするために、関数の部分的な適用にバインドを使用する例を見てみましょう

 function f(x, y, z) { console.log(x + y + z); } var bound = f.bind(null, 3, 5); //     -    ,     this   f,      -      null bound(7); //  15 (3 + 5 + 7) bound(17); //  25 (3 + 5 + 17)
      
      







例からわかるように、関数の部分的な適用の本質は簡単です。バインド関数を使用して最初の引数を「修正」することにより、引数の数を減らして新しい関数を作成します。

これで記事を終了できますが、... bindメソッドを使用して取得した関数には、動作にいくつかの機能があります



2.3。 バインド機能


前の記事へのコメントでは、バインドに関する2つの例が示されました( onetwo )。

これらの例を組み合わせて、文字列の値を同時に変更することにしました。

例(答えを推測してみてください)

 function ClassA() { console.log(this.x, arguments) } ClassA.prototype.x = "fromProtoA"; var ClassB = ClassA.bind({x : "fromBind"}, "bindArg"); ClassB.prototype = {x : "fromProtoB" }; new ClassA("callArg"); new ClassB("callArg"); ClassB("callArg"); ClassB.call({x: "fromCall"}, 'callArg');
      
      





答え
fromProtoA ["callArg"]

fromProtoA ["bindArg"、 "callArg"]

fromBind ["bindArg"、 "callArg"]

fromBind ["bindArg"、 "callArg"]



分解する前に、 標準に従ってバインドの主な機能をリストします。



2.3.1。 固有のプロパティ


Function.prototype.bindによって作成された関数オブジェクトには、プロトタイププロパティまたは内部プロパティ[[Code]]、[[FormalParameters]]、[[Scope]]はありません。



この制限により、 バインドの組み込み実装と手動で定義されたメソッド( MDNのバリアントなど )が区別されます



2.3.2。 電話して申し込む


呼び出しおよび適用メソッドの動作は、関数の標準の動作とは異なります。

 boundFn.[[Call]] = function (thisValue, extraArgs): var boundArgs = boundFn.[[BoundArgs]], boundThis = boundFn.[[BoundThis]], targetFn = boundFn.[[TargetFunction]], args = boundArgs.concat(extraArgs); return targetFn.[[Call]](boundThis, args);
      
      





コードは、 thisValueがどこでも使用されていないことを示しています。 したがって、 call and applyを使用してFunction.prototype.bindを使用して受信した関数の呼び出しコンテキストを変更することできません!



2.3.3。 コンストラクター内


コンストラクターでは、 これは新しい(作成された)オブジェクトを参照します。 つまり、 bindで指定されたコンテキストは単に無視されます。 コンストラクターは、 元の関数通常の[[Call]]を呼び出します

重要! コンストラクターにreturn thisがない場合、戻り値は通常未定義であり、新しい関数の戻り値に依存します!



例の解析


 function ClassA() { console.log(this.x, arguments) } ClassA.prototype.x = "fromProtoA"; var ClassB = ClassA.bind({x : "fromBind"}, "bindArg"); //   2.3.1,         built-in  Function.prototype.bind //     bind (,   MDN)     ClassB.prototype = {x : "fromProtoB" }; //    -  bind     // : fromProtoA ["callArg"] new ClassA("callArg"); //   2.3.3 - this    .   bind    bindArg,         // : fromProtoA ["bindArg", "callArg"] //    bind   : fromBind ["bindArg", "callArg"]. new ClassB("callArg"); //   bind ,      {x : "fromBind"},   bindArg (  bind),  - "callArg" // : fromBind ["bindArg", "callArg"] ClassB("callArg"); //   2.3.2. ,     call  ,    bind   . // : fromBind ["bindArg", "callArg"] ClassB.call({x: "fromCall"}, 'callArg');
      
      







おわりに



この投稿では、コンテキストを関数にリンクする基本的な方法を説明し、Function.prototype.bindの機能のいくつかの機能についても説明しましたが、重要な詳細のみを残そうとしました(私の観点から)。

エラー/不正確に気づいた場合、または何かを明確化/追加したい場合-PMに書き込み、修正



All Articles