Wie kann man Funktionsargumente binden, ohne dies zu binden?

115

Wie kann ich in Javascript Argumente an eine Funktion binden, ohne den thisParameter zu binden ?

Beispielsweise:

//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?

Wie kann ich den Nebeneffekt vermeiden, dass der Funktionsumfang (z. B. Einstellung this= Null) ebenfalls gebunden werden muss?

Bearbeiten:

Entschuldigung für die Verwirrung. Ich möchte Argumente binden, dann die gebundene Funktion später aufrufen und sie genau so verhalten lassen, als hätte ich die ursprüngliche Funktion aufgerufen und ihr die gebundenen Argumente übergeben:

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?

Ich bin noch neu in diesem Bereich, entschuldige die Verwirrung.

Vielen Dank!

Imbue
quelle
1
Wäre es nicht ausreichend, nur neu zu binden this? var b = c.bind(this, 1,2,3);
Kojiro
Warum ist es problematisch, den ersten Wert von bind()sein zu haben null? Scheint in FF gut zu funktionieren.
Russ
7
Wenn die Funktion diese Bindung bereits hat, bringt die Verwendung von null als erstes Argument für bind die Dinge durcheinander (dh eine Objektmethode an den globalen Bereich binden).
Imbue
1
Mir ist wirklich unklar, was Sie versuchen zu tun. Sie können thisin JavaScript nicht vollständig ungebunden sein. Es bedeutet immer etwas . Eine Bindung thisan die thiseinschließende Funktion ist daher sehr sinnvoll.
Kojiro
Ich bin ziemlich neu bei JS. Ich habe die Frage bearbeitet, um sie klarer zu machen. Es kann sein, dass das, was ich will, nicht möglich ist. Vielen Dank.
Imbue

Antworten:

32

Sie können dies tun, aber am besten vermeiden Sie es, es als "bindend" zu betrachten, da dies der Begriff ist, der zum Festlegen des Werts "this" verwendet wird. Stellen Sie sich das vielleicht als "Umhüllung" der Argumente in eine Funktion vor?

Sie erstellen eine Funktion, in die die gewünschten Argumente über Closures integriert sind:

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

Hoffe, ich habe die Syntax genau dort. Es wird eine Funktion namens withWrappedArguments () erstellt (um pedantisch zu sein, es handelt sich um eine anonyme Funktion, die der Variablen zugewiesen ist), die Sie jederzeit und überall aufrufen können und immer mit actualArg1Value und actualArg2Value sowie mit allen anderen Elementen arbeiten, die Sie eingeben möchten Dort. Sie können auch weitere Argumente zum Zeitpunkt des Aufrufs akzeptieren lassen, wenn Sie möchten. Das Geheimnis sind die Klammern nach der letzten schließenden Klammer. Diese bewirken, dass die äußere Funktion mit den übergebenen Werten sofort ausgeführt wird und die innere Funktion generiert wird, die später aufgerufen werden kann. Die übergebenen Werte werden dann zum Zeitpunkt der Generierung der Funktion eingefroren.

Dies ist effektiv das, was bind tut, aber auf diese Weise ist es explizit, dass die umschlossenen Argumente einfach Verschlüsse für lokale Variablen sind, und es besteht keine Notwendigkeit, das Verhalten davon zu ändern.

Ian Nartowicz
quelle
16
Beachten Sie, dass dies normalerweise als "Currying" bezeichnet wird.
M3D
@ M3D Großschreibung, die es so komisch aussehen lässt.
Andrew
Dies wäre viel hilfreicher, wenn ein Beispiel für die Verwendung bereitgestellt würde.
Andrew
en.wikipedia.org/wiki/Currying Link für jeden, der interessiert ist, obwohl die TLDR Version ist „Anstatt f(x,y)wir wollen f(x)(y)oder g=f(x); g(y). Also wir ändern würde f=(x,y)=>x+yzu f=(x)=>(y)=>x+y.“
M3D
26

In ES6 ist dies einfach mithilfe von Ruheparametern in Verbindung mit dem Spread-Operator möglich .

Wir können also eine Funktion definieren bindArgs, die wie folgt funktioniert bind, außer dass nur Argumente gebunden sind, nicht aber der Kontext ( this).

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

Dann für eine bestimmte Funktion foound ein Objekt objdie Anweisung

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

ist äquivalent zu

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

Dabei werden nur das erste und das zweite Argument gebunden bar, während der objim Aufruf angegebene Kontext verwendet wird und zusätzliche Argumente nach den gebundenen Argumenten angehängt werden. Der Rückgabewert wird einfach weitergeleitet.

GOTO 0
quelle
11
bringt es6 auf den Tisch, benutzt immer noch var :(
Daniel Kobe
7
@ DanielKobe Danke! Spread-Operatoren und Rest-Parameter wurden lange zuvor in Firefox implementiert letund constwaren zum Zeitpunkt meiner ersten Veröffentlichung noch nicht verfügbar. Es ist jetzt aktualisiert.
GOTO 0
Das ist großartig und funktioniert für Rückruffunktionen! Kann auch geändert werden, um Argumente anzuhängen, anstatt sie voranzustellen.
DenisM
1
Beachten Sie, dass dies bei jedem Aufruf von bindArgs () zu einem Abschluss führt. Dies bedeutet, dass die Leistung nicht so gut ist wie bei Verwendung der nativen bind () -Funktionalität.
Joshua Walsh
17

Bei der nativen bindMethode geht der thisWert in der Ergebnisfunktion verloren. Sie können den allgemeinen Shim jedoch leicht neu codieren, um kein Argument für den Kontext zu verwenden:

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;
};
Bergi
quelle
4
Ich mag die Tatsache nicht, dass Sie mit dem Prototyp von spielen müssen, Functionaber es hat mir viel Zeit und einige hässliche Problemumgehungen gespart. Danke
Jackdbernier
3
@jackdbernier: Das musst du nicht, aber ich finde es intuitiver als eine Methode wie bind. Sie können es leicht in einfunction bindArgs(fn){ …, args = slice.call(arguments, 1); …}
Bergi
@ Qantas94Heavy: Das musst du immer tun, wenn es dir wichtig thisist a. Sie können bindnatürlich stattdessen verwenden. Das OP kümmert sich jedoch ausdrücklich nicht um den thisWert und wollte eine ungebundene Teilfunktion.
Bergi
@Bergi: Ich habe angenommen, dass der Zweck der Nichteinstellung darin thisbesteht, dass die Funktion verwendet wird this, aber sie wollten sie in diesem Moment nicht binden. Auch das Bit in der Frage des OP //These should both have exact same output.widerspricht anderen Teilen davon.
Qantas 94 Heavy
1
@KubaWyrostek: Ja, es geht darum, dass Teilkonstruktorfunktionen funktionieren, ähnlich wie Unterklassen (nicht, dass ich dies empfehle). Ein Ist bindfunktioniert überhaupt nicht mit Konstruktoren, die gebundenen Funktionen haben keine .prototype. Nein,Function.prototype !== mymethod.prototype
Bergi
7

Eine weitere winzige Implementierung nur zum Spaß:

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);
    };
}

Wie benutzt man:

function onWriteEnd(evt) {}
var myPersonalWriteEnd = bindWithoutThis(onWriteEnd, "some", "data");
Dmitrii Sorin
quelle
Scheint, als wäre es richtiger, beim Aufrufen von apply () "null" zu übergeben
eddiewould
5
var b = function() {
    return c(1,2,3);
};
Mike
quelle
3
Bei all diesen lauten und mehrdeutigen Antworten ist dies eine präzise und hilfreiche Antwort. Dafür +1. Vielleicht könnten Sie den Stil der Antwort verfeinern, um sie für das bloße Auge in diesem ausgefallenen lauten Rest des Dschungels attraktiver zu machen. const curry = (fn, ...curryArgs) => (...args) => fn(...curryArgs, args)
Joehannes
2

Es ist ein bisschen schwierig, genau zu sagen, was Sie letztendlich tun möchten, da das Beispiel willkürlich ist, aber Sie möchten vielleicht Partials (oder Currying) untersuchen: 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(){})

Link zu John Resigs Artikel: http://ejohn.org/blog/partial-functions-in-javascript/

Kevin Ennis
quelle
Diese Funktion funktioniert nicht. Sie können eine teilweise angewendete Funktion nur einmal aufrufen.
Bergi
Ich bin mir nicht sicher, ob ich folge. Können Sie das etwas näher erläutern oder ein jsbin-Beispiel posten?
Kevin Ennis
Siehe dieses Beispiel . Außerdem müssen Sie partialmit undefinedArgumenten aufgerufen werden, um zu funktionieren - nicht das, was man erwarten würde, und bei Funktionen, die eine beliebige Anzahl von Parametern annehmen, fehlerhaft sein.
Bergi
Kevin, danke für deine Antwort. Es ist nah an dem, was ich will, aber ich denke, das thisObjekt wird immer noch durcheinander gebracht. Werfen
Imbue
Ohhh, Gotchya. Dieses Beispiel macht viel klarer, wonach Sie suchen.
Kevin Ennis
2

Vielleicht möchten Sie den Verweis darauf in Ihrem vorletzten Code binden : -

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

Bereits angewendete Bindung zum Beispiel dies und später können Sie es nicht ändern. Was ich vorschlagen werde, verwenden Sie die Referenz auch als Parameter wie folgt: -

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);
Deepak Dixit
quelle
1

Verwenden Sie ein 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);
}

Grundsätzlich wird die Funktion, an die Sie Parameter übergeben möchten, zu einem Proxy, den Sie aufrufen können, um eine Variable zu übergeben, und sie gibt die Funktion zurück, die Sie tatsächlich ausführen möchten.

Hoffe das hilft!

Cody
quelle
1

Ein anonymer Benutzer hat diese zusätzlichen Informationen gepostet:

Aufbauend auf dem bereits in diesem Beitrag nicht zur Verfügung gestellt worden - die eleganteste Lösung , die ich gesehen habe , ist Curry Ihre Argumente und Kontext:

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']);
Barett
quelle
1

Nun, für das Beispiel, das Sie gegeben haben, wird dies ausreichen

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

Wenn Sie die Parameter garantieren möchten:

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)

Aber wenn ja, sollten Sie sich einfach an Ihre Lösung halten:

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

Es ist der bessere Weg.

Joseph Garrone
quelle
1

So einfach ist das?

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

Allgemeinere Lösung:

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

Qwerty
quelle
1

Mit LoDash können Sie die _.partialFunktion nutzen.

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

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

pf(function () {})                // callback.
Daniele Orlando
quelle
0

Warum nicht einen Wrapper um die Funktion verwenden, um diese als Mythos zu speichern?

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);
Olivier Archer
quelle
-1

jQuery 1.9 brachte genau diese Funktion mit der Proxy-Funktion.

Ab jQuery 1.9 wird die Proxy-Funktion mit demselben Objekt aufgerufen, mit dem der Proxy aufgerufen wurde, wenn der Kontext null oder undefiniert ist. Dadurch kann $ .proxy () verwendet werden, um die Argumente einer Funktion teilweise anzuwenden, ohne den Kontext zu ändern.

Beispiel:

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

Jquery Anwendungsfall:

stattdessen:

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

benutze das:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(e){
        var i = this;
        $(e.originalEvent.target).val(i);
    }.bind(i));
}

quelle