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.