Jak powiązać argumenty funkcji bez wiązania tego?


115

W JavaScript, jak mogę powiązać argumenty z funkcją bez wiązania thisparametru?

Na przykład:

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

Jak mogę uniknąć efektu ubocznego związanego z koniecznością wiązania zakresu funkcji (np. Ustawienie this= null) również?

Edytować:

Przepraszam za zamieszanie. Chcę powiązać argumenty, a następnie móc później wywołać powiązaną funkcję i sprawić, by zachowywała się dokładnie tak, jak gdybym wywołała oryginalną funkcję i przekazała jej powiązane argumenty:

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?

Nadal jestem w tym nowy, przepraszam za zamieszanie.

Dzięki!


1
Czy ponowne wiązanie byłoby niewystarczające this? var b = c.bind(this, 1,2,3);
— kojiro,

Dlaczego posiadanie pierwszej wartości bind()be jest problematyczne null? Wydaje się, że działa dobrze w FF.
— Russ

7
Jeśli funkcja ma już to ograniczenie, użycie wartości null jako pierwszego argumentu bind zepsuje wszystko (np. Powiąże metodę obiektu z zasięgiem globalnym).
— Imbue

1
Naprawdę nie wiem, co próbujesz zrobić. Nie możesz thiscałkowicie uwolnić się w JavaScript. To zawsze coś znaczy . Zatem wiązanie thissię thisz funkcją zamykającą ma dużo sensu.
— kojiro

Jestem całkiem nowy w JS. Zredagowałem pytanie, aby było jaśniejsze. Być może to, czego chcę, nie jest możliwe. Dzięki.
— Imbue

Odpowiedzi:


32

Możesz to zrobić, ale najlepiej unikać myślenia o tym jako o „wiążącym”, ponieważ jest to termin używany do ustawiania wartości „this”. Może pomyśl o tym jako o „zawijaniu” argumentów do funkcji?

To, co robisz, to tworzenie funkcji, która ma żądane argumenty wbudowane w domknięcia:

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

Mam nadzieję, że mam tutaj składnię. To, co robi, to utworzenie funkcji o nazwie withWrappedArguments () (mówiąc pedantycznie, jest to funkcja anonimowa przypisana do zmiennej), którą możesz wywołać w dowolnym miejscu i zawsze będzie działać z rzeczywistymiArg1Value i realArg2Value oraz wszystkim innym, co chcesz wstawić tam. Możesz również poprosić o akceptację dalszych argumentów w czasie połączenia, jeśli chcesz. Sekret tkwi w nawiasach po ostatnim nawiasie zamykającym. Powodują one natychmiastowe wykonanie funkcji zewnętrznej z przekazanymi wartościami i wygenerowanie funkcji wewnętrznej, którą można później wywołać. Przekazane wartości są następnie zamrażane w momencie generowania funkcji.

To właśnie robi bind, ale w ten sposób jest jasne, że opakowane argumenty są po prostu domknięciami dla zmiennych lokalnych i nie ma potrzeby zmiany tego zachowania.


16
Zwróć uwagę, że jest to zwykle nazywane „curry”.
— M3D

@ M3D Kapitalizacja sprawia, że ​​wygląda to tak dziwnie.
— Andrew,

Byłoby to o wiele bardziej pomocne, gdyby podano przykład, jak go używać.
— Andrew,

en.wikipedia.org/wiki/Currying link dla każdego, kto jest zainteresowany, chociaż wersja TLDR to „Zamiast tego f(x,y)chcemy f(x)(y)lub g=f(x); g(y). Więc zmienilibyśmy f=(x,y)=>x+yna f=(x)=>(y)=>x+y”.
— M3D

26

W ES6 można to łatwo zrobić za pomocą parametrów odpoczynku w połączeniu z operatorem spreadu .

Możemy więc zdefiniować funkcję, bindArgsktóra działa jak bind, z tym wyjątkiem, że powiązane są tylko argumenty, ale nie context ( this).

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

Następnie dla określonej funkcji fooi obiektu objinstrukcja

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

jest równa

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

gdzie tylko pierwszy i drugi argument są powiązane bar, podczas gdy kontekst objokreślony w wywołaniu jest używany, a dodatkowe argumenty są dołączane po powiązanych argumentach. Wartość zwracana jest po prostu przekazywana dalej.


11
przynosi es6 do stołu, nadal używa var :(
— Daniel Kobe

7
@DanielKobe Thanks! Operatory rozprzestrzeniania i pozostałe parametry zostały zaimplementowane w Firefoksie dużo wcześniej leti constnie były jeszcze dostępne, zanim to opublikowałem. Jest teraz zaktualizowany.
— GOTO 0

To jest świetne i działa w przypadku funkcji zwrotnych! Można go również zmienić, aby dodawać argumenty zamiast poprzedzać je.
— DenisM

1
Warto zauważyć, że powoduje to zamknięcie za każdym razem, gdy wywoływana jest funkcja bindArgs (), co oznacza, że ​​wydajność nie jest tak dobra, jak przy użyciu natywnej funkcji bind ().
— Joshua Walsh

17

W rodzimej bindmetodythis wartość w funkcji wynikowej jest stracone. Możesz jednak łatwo przekodować zwykłą podkładkę, aby nie używać argumentu dla kontekstu:

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
Nie podoba mi się fakt, że musisz bawić się prototypem, Functionale zaoszczędził mi to dużo czasu i kilka brzydkich obejść. Dzięki
— jackdbernier

3
@jackdbernier: Nie musisz, ale uważam, że jest to bardziej intuicyjne jako metoda bind. Możesz łatwo przekształcić go wfunction bindArgs(fn){ …, args = slice.call(arguments, 1); …}
— Bergi

@ Qantas94Heavy: To co zawsze musisz zrobić, kiedy obchodzi thissię a. Możesz bindoczywiście użyć zamiast tego. Jednak PO wyraźnie nie dba o thiswartość i chciał mieć nieograniczoną funkcję częściową.
— Bergi

@Bergi: Zakładałem, że celem nie ustawiania thisjest to, że funkcja używa this, ale w tym momencie nie chcieli tego wiązać. Również fragment pytania PO //These should both have exact same output.jest sprzeczny z innymi częściami.
— Qantas 94 Heavy

1
@KubaWyrostek: Tak, chodzi o to, żeby działały częściowe funkcje konstruktora, coś w rodzaju podklasy (chociaż nie polecam tego). Rzeczywistość w bindogóle nie działa z konstruktorami, powiązane funkcje nie mają .prototype. Nie,Function.prototype !== mymethod.prototype
— Bergi

7

Jeszcze jedna mała implementacja dla zabawy:

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

Jak używać:

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

Wydaje się, że bardziej poprawne byłoby przekazanie "null" dla tego przy wywoływaniu Apply ()
— eddiewould

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

3
W tych wszystkich hałaśliwych i niejednoznacznych odpowiedziach jest to precyzyjna i pomocna odpowiedź. Dlatego +1. Może mógłbyś udoskonalić styl odpowiedzi, aby była bardziej atrakcyjna dla gołego oka w tej fantazyjnej, hałaśliwej części dżungli. const curry = (fn, ...curryArgs) => (...args) => fn(...curryArgs, args)
— Joehannes,

2

Trochę trudno jest dokładnie określić, co ostatecznie chcesz zrobić, ponieważ przykład jest w pewnym sensie arbitralny, ale możesz zajrzeć do częściowych (lub curry): 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 do artykułu Johna Resiga: http://ejohn.org/blog/partial-functions-in-javascript/


Ta funkcja nie działa, nie można wywołać częściowo zastosowanej funkcji więcej niż raz.
— Bergi

Nie jestem pewien, czy nadążam. Czy możesz to trochę rozwinąć lub opublikować przykład jsbin?
— Kevin Ennis

Zobacz ten przykład . Ponadto, twoje partialpotrzeby muszą być wywoływane z undefinedargumentami, aby działały - nie takie, jakich można by się spodziewać, i łamane w funkcjach, które przyjmują dowolną liczbę parametrów.
— Bergi

Kevin, dziękuję za odpowiedź. Jest blisko tego, czego chcę, ale myślę, że thisobiekt nadal się popsuje. Spójrz na: jsbin.com/ifoqoj/4/edit
— Imbue

Ohhh, gotchya. Ten przykład znacznie wyjaśnia, czego szukasz.
— Kevin Ennis

2

Może chcesz powiązać odniesienie do tego w przedostatnim kodzie: -

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

Już zastosowane wiązanie na przykład to i później nie można go zmienić. Co zasugeruję, aby użyć odwołania również jako parametru takiego: -

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

Użyj 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);
}

Zasadniczo funkcja, do której chcesz przekazać parametry, staje się proxy, które możesz wywołać, aby przekazać zmienną, i zwraca funkcję, którą faktycznie chcesz wykonać.

Mam nadzieję że to pomoże!


1

Anonimowy użytkownik opublikował te dodatkowe informacje:

Opierając się na tym, co już zostało przedstawione w tym poście - najbardziej eleganckim rozwiązaniem, jakie widziałem, jest curry twoich argumentów i kontekstu:

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

Cóż, na przykład, który podałeś, to wystarczy

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

Jeśli chcesz zagwarantować załączenie parametrów:

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)

Ale jeśli tak, to po prostu trzymaj się swojego rozwiązania:

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

To lepszy sposób.


1

Tak proste?

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

Bardziej ogólne rozwiązanie:

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

Za pomocą LoDash możesz skorzystać z _.partialfunkcji.

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

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

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

0

Dlaczego nie użyć opakowania wokół funkcji, aby zapisać to jako mit?

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 przyniósł dokładnie tę funkcję z funkcją proxy.

Począwszy od jQuery 1.9, gdy kontekst jest pusty lub nieokreślony, funkcja proxy zostanie wywołana z tym samym obiektem, z którym wywołano proxy. Pozwala to na użycie $ .proxy () do częściowego zastosowania argumentów funkcji bez zmiany kontekstu.

Przykład:

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

-5

Przypadek użycia jquery:

zamiast:

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

Użyj tego:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(e){
        var i = this;
        $(e.originalEvent.target).val(i);
    }.bind(i));
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.