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?
.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.
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.
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);
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.
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.
Ich finde folgende sauberer als die akzeptierten Antworten
Function.bind.apply(db.find, [null].concat(arguments));
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
}
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);
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.
Eine endgültige und einfache Antwort könnte sein
Function.apply.bind(this.method, this, arguments);
Etwas "schwer" zu fassen, aber ordentlich.