これをバインドせずに関数の引数をバインドする方法は?


115

JavaScriptでは、thisパラメータをバインドせずに関数に引数をバインドするにはどうすればよいですか?

例えば:

//Example function.
var c = function(a, b, c, callback) {};

//Bind values 1, 2, and 3 to a, b, and c, leave callback unbound.
var b = c.bind(null, 1, 2, 3); //How can I do this without binding scope?

関数のスコープをバインドする必要があるという副作用(たとえば、setting this= null)を回避するにはどうすればよいですか?

編集:

混乱させて申し訳ありません。引数をバインドして、後でバインドされた関数を呼び出して、元の関数を呼び出してバインドされた引数を渡した場合とまったく同じように動作させることができます。

var x = 'outside object';

var obj = {
  x: 'inside object',
  c: function(a, b, c, callback) {
    console.log(this.x);
  }
};

var b = obj.c.bind(null, 1, 2, 3);

//These should both have exact same output.
obj.c(1, 2, 3, function(){});
b(function(){});

//The following works, but I was hoping there was a better way:
var b = obj.c.bind(obj, 1, 2, 3); //Anyway to make it work without typing obj twice?

まだ混乱しているので、まだ新しいです。

ありがとう!


1
再バインドするだけでは不十分でしょうthisか?var b = c.bind(this, 1,2,3);
kojiro

最初の値がbind()be であることがなぜ問題があるのnullですか?FFで正常に動作するようです。
ラス

7
関数にすでにこのバインドがある場合、bindの最初の引数としてnullを使用すると、処理が乱れます(つまり、オブジェクトメソッドをグローバルスコープにバインドします)。
Imbue

1
あなたが何をしようとしているのか本当にわかりません。thisJavaScriptで完全にバインド解除することはできません。それは常に何かを意味します。したがって、囲み関数のthisへのバインドは、非常thisに理にかなっています。
kojiro

私はJSでかなり新しいです。質問をより明確にするために編集しました。自分のやりたいことができないのかもしれません。ありがとう。
Imbue

回答:


32

これは可能ですが、「バインド」とは考えないでください。これは「this」値を設定するために使用される用語であるためです。おそらく、それを引数を関数に「ラップする」と考えていますか?

あなたがすることは、クロージャーを介してそれに組み込まれた望ましい引数を持つ関数を作成することです:

var withWrappedArguments = function(arg1, arg2)
    {
        return function() { ... do your stuff with arg1 and arg2 ... };
    }(actualArg1Value, actualArg2Value);

構文がそこにあることを願っています。それがすることはwithWrappedArguments()と呼ばれる関数を作成することです(具体的には変数に割り当てられた無名関数です)いつでもどこでも呼び出すことができ、actualArg1ValueとactualArg2Value、そしてあなたが入れたい他の何かで常に動作しますそこ。必要に応じて、呼び出し時にさらに引数を受け入れるようにすることもできます。秘密は、最後の右中括弧の後の括弧です。これらにより、渡された値を使用して外部関数がすぐに実行され、後で呼び出すことができる内部関数が生成されます。渡された値は、関数の生成時に凍結されます。

これは事実上bindが行うことですが、この方法では、ラップされた引数がローカル変数の単なるクロージャーであり、これの動作を変更する必要がないことは明白です。


16
これは通常「カレー」と呼ばれることに注意してください。
M3D 2017

@ M3D大文字にすると、奇妙に見えます。
Andrew

これを使用する方法の例が提供されている場合、これははるかに役立ちます。
Andrew

en.wikipedia.org/wiki/TLDRバージョンは「f(x,y)私たちが欲しいf(x)(y)g=f(x); g(y)。ではf=(x,y)=>x+yなくf=(x)=>(y)=>x+y。に変更する予定です」ですが、興味のある人のためのカレーリンク。
M3D

26

ES6では、これは、スプレッドオペレーターと組み合わせてレストパラメーターを使用して簡単に実行できます。

したがって、引数のみがバインドされ、コンテキストはバインドされないことを除いて、のbindArgsようbindに機能する関数を定義できます(this)。

Function.prototype.bindArgs =
    function (...boundArgs)
    {
        const targetFunction = this;
        return function (...args) { return targetFunction.call(this, ...boundArgs, ...args); };
    };

次に、指定された関数fooとオブジェクトobjに対して、ステートメント

return foo.call(obj, 1, 2, 3, 4);

に相当

let bar = foo.bindArgs(1, 2);
return bar.call(obj, 3, 4);

ここでは、最初と2番目の引数のみがにバインドされますが、呼び出しで指定されたbarコンテキストobjが使用され、バインドされた引数の後に追加の引数が追加されます。戻り値は単純に転送されます。


11
テーブルにES6をもたらし、まだVARを使用しています:(
ダニエル・神戸

7
@DanielKobeありがとう!スプレッド演算子と休息のparamsは、ずっと前にFirefoxで実施されたletconst、利用できなかったこれはまだ時間によって私は最初の投稿です。現在更新されています。
GOTO 0

これは素晴らしく、コールバック関数で機能します!引数を前に付けるのではなく、追加するように変更することもできます。
DenisM 2017

1
bindArgs()が呼び出されるたびにこれがクロージャーを作成することは注目に値します。つまり、パフォーマンスはネイティブのbind()機能を使用する場合ほど良くありません。
ジョシュアウォルシュ

17

ネイティブではbindメソッドthisの結果関数の値が失われます。ただし、コンテキストに引数を使用しないように一般的なシムを簡単に再コーディングできます。

Function.prototype.arg = function() {
    if (typeof this !== "function")
        throw new TypeError("Function.prototype.arg needs to be called on a function");
    var slice = Array.prototype.slice,
        args = slice.call(arguments), 
        fn = this, 
        partial = function() {
            return fn.apply(this, args.concat(slice.call(arguments)));
//                          ^^^^
        };
    partial.prototype = Object.create(this.prototype);
    return partial;
};

4
のプロトタイプをいじる必要があるという事実は気に入らないFunctionが、それによって多くの時間といくつかの醜い回避策が節約された。ありがとう
jackdbernier

3
@jackdbernier:必ずしもそうする必要はありませんが、のような方法の方が直感的bindです。あなたは簡単にそれをaに変換することができますfunction bindArgs(fn){ …, args = slice.call(arguments, 1); …}
ベルギ2013年

@ Qantas94Heavy:気thisになるときにいつもしなければならないことaです。bindもちろん代わりに使うかもしれません。それでも、OPは明示的に値を気にせthis、バインドされていない部分関数を必要としていました。
Bergi

@Bergi:設定しないことの目的はthis、関数がを使用thisすることであると想定していましたが、その時点ではバインドしたくありませんでした。また、OPの質問//These should both have exact same output.の一部は、OPの他の部分と矛盾しています。
Qantas 94 Heavy

1
@KubaWyrostek:はい、それはサブコンストラクターのような部分的なコンストラクター関数を機能させることです(ただし、これはお勧めしません)。実際bindはコンストラクターではまったく機能せず、バインドされた関数にはがありません.prototype。いいえ、Function.prototype !== mymethod.prototype
ベルギ2014

7

楽しみのためのもう1つの小さな実装:

function bindWithoutThis(cb) {
    var bindArgs = Array.prototype.slice.call(arguments, 1);

    return function () {
        var internalArgs = Array.prototype.slice.call(arguments, 0);
        var args = Array.prototype.concat(bindArgs, internalArgs);
        return cb.apply(this, args);
    };
}

使い方:

function onWriteEnd(evt) {}
var myPersonalWriteEnd = bindWithoutThis(onWriteEnd, "some", "data");

apply()を呼び出すときにこれに「null」を渡す方が正しいようです
eddiewould

6
var b = function() {
    return c(1,2,3);
};

3
このすべてのうるさくて曖昧な答えの中で、これは正確で役立つ答えです。そのため+1。ジャングルの派手で騒々しい残りの部分で、答えのスタイルを洗練して肉眼でより魅力的にすることができます。const curry = (fn, ...curryArgs) => (...args) => fn(...curryArgs, args)
Joehannes 2017

2

この例は恣意的であるため、最終的に何をしたいのか正確に伝えるのは少し難しいですが、パーシャル(またはカリー化)を調べることをお勧めします。http://jsbin.com/ifoqoj/1/edit

Function.prototype.partial = function(){
  var fn = this, args = Array.prototype.slice.call(arguments);
  return function(){
    var arg = 0;
    for ( var i = 0; i < args.length && arg < arguments.length; i++ )
      if ( args[i] === undefined )
        args[i] = arguments[arg++];
    return fn.apply(this, args);
  };
};

var c = function(a, b, c, callback) {
  console.log( a, b, c, callback )
};

var b = c.partial(1, 2, 3, undefined);

b(function(){})

John Resigの記事へのリンク:http : //ejohn.org/blog/partial-functions-in-javascript/


この関数は機能しません。部分的に適用された関数を複数回呼び出すことはできません。
ベルギ

よくわからない それについて少し詳しく説明したり、jsbinの例を投稿したりできますか?
Kevin Ennis

この例を参照してください。また、機能するpartialためにはundefined引数を指定して呼び出す必要があります-期待するものではなく、任意の数のパラメーターを取る関数で壊れています。
Bergi

ケビン、答えてくれてありがとう。それは私が欲しいものに近いですが、thisオブジェクトはまだめちゃくちゃになっていると思います。見てみましょう:jsbin.com/ifoqoj/4/editを
インビュー

ああ、ゲッチャ。その例は、あなたが探しているものをより明確にします。
Kevin Ennis

2

最後にこれの参照をバインドしたいかもしれませんが、あなたのコード:-

var c = function(a, b, c, callback) {};
var b = c.bind(null, 1, 2, 3); 

すでに適用されているバインドは、たとえばこれ以降、変更することはできません。このようなパラメータとしても参照を使用することをお勧めします:-

var c = function(a, b, c, callback, ref) {  
    var self = this ? this : ref; 
    // Now you can use self just like this in your code 
};
var b = c.bind(null, 1, 2, 3),
    newRef = this, // or ref whatever you want to apply inside function c()
    d = c.bind(callback, newRef);

1

使用してくださいprotagonist

var geoOpts = {...};

function geoSuccess(user){  // protagonizes for 'user'
  return function Success(pos){
    if(!pos || !pos.coords || !pos.coords.latitude || !pos.coords.longitude){ throw new Error('Geolocation Error: insufficient data.'); }

    var data = {pos.coords: pos.coords, ...};

    // now we have a callback we can turn into an object. implementation can use 'this' inside callback
    if(user){
      user.prototype = data;
      user.prototype.watch = watchUser;
      thus.User = (new user(data));
      console.log('thus.User', thus, thus.User);
    }
  }
}

function geoError(errorCallback){  // protagonizes for 'errorCallback'
  return function(err){
    console.log('@DECLINED', err);
    errorCallback && errorCallback(err);
  }
}

function getUserPos(user, error, opts){
  nav.geo.getPos(geoSuccess(user), geoError(error), opts || geoOpts);
}

基本的に、paramsを渡したい関数は、変数を渡すために呼び出すことができるプロキシになり、実際に処理したい関数を返します。

お役に立てれば!


1

匿名ユーザーがこの追加情報を投稿しました:

この投稿ですでに提供されているものに基づいて構築します-私が見た中で最もエレガントなソリューションは、あなたの主張とコンテキストをカレーすることです:

function Class(a, b, c, d){
    console.log('@Class #this', this, a, b, c, d);
}

function Context(name){
    console.log('@Context', this, name);
    this.name = name;
}

var context1 = new Context('One');
var context2 = new Context('Two');

function curryArguments(fn) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function bindContext() {
      var additional = Array.prototype.slice.call(arguments, 0);
      return fn.apply(this, args.concat(additional));
    };
}

var bindContext = curryArguments(Class, 'A', 'B');

bindContext.apply(context1, ['C', 'D']);
bindContext.apply(context2, ['Y', 'Z']);

1

まあ、あなたが与えた例については、これで十分です

var b= function(callback){
        return obj.c(1,2,3, callback);
};

パラメータの囲い込みを保証したい場合:

var b= (function(p1,p2,p3, obj){
        var c=obj.c;
        return function(callback){
                return c.call(obj,p1,p2,p3, callback);
        }
})(1,2,3,obj)

しかしそうであれば、あなたはあなたの解決策に固執するべきです:

var b = obj.c.bind(obj, 1, 2, 3);

それはより良い方法です。


1

そんなシンプル?

var b = (cb) => obj.c(1,2,3, cb)
b(function(){}) // insidde object

より一般的な解決策:

function original(a, b, c) { console.log(a, b, c) }
let tied = (...args) => original(1, 2, ...args)

original(1,2,3) // 1 2 3
tied(5,6,7) // 1 2 5


1

使用LoDashことは、あなたが使用できる_.partial機能を。

const f  = function (a, b, c, callback) {}

const pf = _.partial(f, 1, 2, 3)  // f has first 3 arguments bound.

pf(function () {})                // callback.

0

関数の周りにラッパーを使用して、これをmythisとして保存してみませんか?

function mythis() {
  this.name = "mythis";
  mythis = this;
  function c(a, b) {
    this.name = "original";
    alert('a=' + a + ' b =' + b + 'this = ' + this.name + ' mythis = ' + mythis.name);
    return "ok";
  }    
  return {
    c: c
  }
};

var retval = mythis().c(0, 1);

-1

jQuery 1.9はまさにその機能をプロキシー機能でもたらしました。

jQuery 1.9以降、コンテキストがnullまたは未定義の場合、プロキシが呼び出されたときと同じthisオブジェクトを使用して、プロキシ関数が呼び出されます。これにより、$。proxy()を使用して、コンテキストを変更せずに関数の引数を部分的に適用できます。

例:

$.proxy(this.myFunction, 
        undefined /* leaving the context empty */, 
        [precededArg1, precededArg2]);

-5

jqueryの使用例:

代わりに:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(i){
        $(this).val(i); // wont work, because 'this' becomes 'i'
    }.bind(i));
}

これを使って:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(e){
        var i = this;
        $(e.originalEvent.target).val(i);
    }.bind(i));
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.