webentwicklung-frage-antwort-db.com.de

Function.prototype.bind mit einem Array von Argumenten verwenden?

Wie kann ich Function.prototype.bind mit einem Array von Argumenten aufrufen, im Gegensatz zu fest codierten Argumenten? (Nicht mit ECMA6, also kein Spread-Operator).

Ich versuche, ein Versprechen-Wrapper um ein Modul zu legen, das Callbacks verwendet, und ich möchte alle übergebenen Argumente an meine Wrapper-Methode binden und sie binden. Dann möchte ich die teilweise angewendete gebundene Funktion mit meinem eigenen Rückruf aufrufen, wodurch ein Versprechen gelöst oder abgelehnt wird.

var find = function() {
  var deferred, bound;
  deferred = Q.defer();
  bound = db.find.bind(null, arguments);
  bound(function(err, docs) {
    if(err) {
      deferred.fail(err);
    } else {
      deferred.resolve(docs);
    }
  });
  return deferred.promise;
}

Dies funktioniert natürlich nicht, weil bind Argumente statt ein Array von Argumenten erwartet. Ich weiß, dass ich dies tun könnte, indem ich meinen Rückruf am Ende des Arguments-Arrays einfügen und "apply" verwenden würde:

arguments[arguments.length] = function(err, docs) { ... }
db.find.apply(null, arguments);

Oder durchlaufen Sie das arguments-Array und binden Sie die Funktion für jedes Argument erneut:

var bound, context;
for(var i = 0; i < arguments.length; i++) {
   context = bound ? bound : db.find;
   bound = context.bind(null, arguments[i]);
}
bound(function(err, docs) { ... })

Beide Methoden wirken jedoch schmutzig. Irgendwelche Ideen?

45
Dan Prince

.bind ist eine normale Funktion, daher können Sie .apply darauf aufrufen.
Alles, was Sie tun müssen, ist, die ursprüngliche Funktion als ersten Parameter und die gewünschte Variable THIS als erstes Element im Array von Argumenten zu übergeben:

bound = db.find.bind.apply(db.find, [null].concat(arguments));
//      ^-----^            ^-----^   THIS

Ob dies sauberer ist oder nicht, bleibt dem Leser überlassen.

75
Felix Kling

Das Folgende ist ein allgemeiner Code-Ausschnitt, den ich in allen meinen Projekten verwende:

var bind = Function.bind;
var call = Function.call;

var bindable = bind.bind(bind);
var callable = bindable(call);

Die bindable-Funktion kann jetzt verwendet werden, um ein Array wie folgt an bind zu übergeben:

var bound = bindable(db.find, db).apply(null, arguments);

Tatsächlich können Sie bindable(db.find, db) zwischenspeichern, um die Bindung wie folgt zu beschleunigen:

var findable = bindable(db.find, db);
var bound = findable.apply(null, arguments);

Sie können die Funktion findable mit oder ohne ein Array von Argumenten verwenden:

var bound = findable(1, 2, 3);

Hoffe das hilft.

11
Aadit M Shah

Die Antwort von Felix hat für mich nicht funktioniert, da das arguments-Objekt nicht wirklich ein Array ist (wie Otts hervorgehoben hat). Die Lösung für mich war, einfach bind und apply zu wechseln:

bound = db.find.apply.bind(db.find, null, arguments);
9
Joel

Für diejenigen, die ES6 verwenden, kompiliert Babel:

db.find.bind(this, ...arguments)

zu:

db.find.bind.apply(db.find, [this].concat(Array.prototype.slice.call(arguments)));

Ich denke, es ist fair zu sagen, dass Babel ziemlich definitiv ist. Dank an @ lorenz-lo-sauer, es ist fast identisch.

4
nathancahill

Warum binden Sie nicht einfach an das Argument-Array wie in Ihrem Beispiel, und lassen Sie es von der Funktion bound() genauso wie als Array behandeln.

So wie es aussieht, übergeben Sie eine Funktion als letztes Argument an bound(). Das heißt, durch die Übergabe des eigentlichen Argument-Arrays vermeiden Sie, Argumente von Callbacks in bound() zu trennen, wodurch das Spielen möglicherweise einfacher wird.

1
Matt Way

Ich finde folgende sauberer als die akzeptierten Antworten

Function.bind.apply(db.find, [null].concat(arguments));
1
brillout

Wenn jemand nach einem abstrakten Muster sucht:

var binded = hello.apply.bind(hello,null,['hello','world']);

binded();

function hello(a,b){
  console.log(this); //null
  console.log(a); //hello
  console.log(b); //world
}
1
Paweł

Im Allgemeinen reicht dieses Schema aus:

//obj = db
//fnName = 'find'
var args = [this||null].concat(Array.prototype.slice.apply(arguments);
obj[fnName].bind.apply(obj[fnName], args);
1
Lorenz Lo Sauer

Hatte gerade eine alternative Idee, wende teilweise den null-Wert für context an und benutze dann apply, um die teilweise angewendete Funktion aufzurufen.

bound = db.find.bind.bind(null).apply(null, arguments);

Dies macht die etwas gruselig aussehende [null].concat() in der Antwort von @ Felix überflüssig.

0
Dan Prince

Eine endgültige und einfache Antwort könnte sein

Function.apply.bind(this.method, this, arguments);

Etwas "schwer" zu fassen, aber ordentlich.

0
131