Bunu bağlamadan işlev bağımsız değişkenleri nasıl bağlanır?


115

Javascript'te, thisparametreyi bağlamadan bağımsız değişkenleri bir işleve nasıl bağlayabilirim ?

Örneğin:

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

Fonksiyonun kapsamını (örneğin ayar this= null) bağlamanın yan etkisinden nasıl kaçınabilirim ?

Düzenle:

Karışıklık için özür dilerim. Argümanları bağlamak, ardından bağlı işlevi daha sonra çağırmak ve tam olarak orijinal işlevi çağırıp ona bağlı argümanları iletmişim gibi davranmasını sağlamak istiyorum:

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?

Bu konuda hala yeniyim, karışıklık için üzgünüm.

Teşekkürler!


1
Yeniden bağlanmak yetersiz olur thismu? var b = c.bind(this, 1,2,3);
kojiro

Be'nin ilk değerine sahip bind()olmak neden sorunludur null? FF'de iyi çalışıyor gibi görünüyor.
Russ

7
İşlev zaten bu sınıra sahipse, bind'in ilk argümanı olarak null kullanmak işleri karıştırır (yani bir nesne yöntemini genel kapsama bağlar).
2012'de 0

1
Ne yapmaya çalıştığını gerçekten bilmiyorum. thisJavaScript'te tamamen bağlantısız olamazsınız . Her zaman anlamına şey . Bağlama Yani thisiçin thisçevreleyen fonksiyonunun anlamda bir çok yapar.
kojiro

JS'de oldukça yeniyim. Daha net hale getirmek için soruyu düzenledim. İstediğim şey mümkün değil olabilir. Teşekkürler.
2012'de

Yanıtlar:


32

Bunu yapabilirsiniz, ancak en iyisi bunu "bağlayıcı" olarak düşünmekten kaçınmaktır çünkü "bu" değeri ayarlamak için kullanılan terim budur. Belki de argümanları bir işleve "sarmak" olarak düşünün?

Yaptığınız şey, kapanışlar aracılığıyla yerleşik olarak istenen argümanlara sahip bir işlev oluşturmaktır:

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

Umarım sözdizimini tam orada buldum. Yaptığı şey, withWrappedArguments () adında bir işlev oluşturmaktır (bilgiçlik göstermek gerekirse, değişkene atanan anonim bir işlevdir), istediğiniz zaman istediğiniz yerde arayabilir ve her zaman gerçekArg1Value ve gerçekArg2Value ile ve eklemek istediğiniz diğer herhangi bir şeyle hareket eder. Orada. İsterseniz arama sırasında başka argümanları da kabul etmesini sağlayabilirsiniz. İşin sırrı, son kapanış ayracından sonraki parantezlerdir. Bunlar, dış işlevin hemen aktarılan değerlerle yürütülmesine ve daha sonra çağrılabilecek iç işlevi oluşturmasına neden olur. Aktarılan değerler daha sonra işlevin üretildiği anda dondurulur.

Bu, bind'in etkin bir şekilde yaptığı şeydir, ancak bu şekilde, sarılmış argümanların basitçe yerel değişkenler üzerindeki kapanışlar olduğu ve bunun davranışını değiştirmeye gerek olmadığı açıktır.


16
Bunun genellikle "Currying" olarak adlandırıldığını unutmayın.
M3D

@ M3D Kapitalize etmek çok tuhaf görünmesini sağlıyor.
Andrew

Nasıl kullanılacağına dair bir örnek verilseydi bu çok daha yararlı olurdu.
Andrew

en.wikipedia.org/wiki/Currying TLDR versiyonu olmasına rağmen, ilgi duyan herkes bağlantısını "Yerine f(x,y)istediğimiz f(x)(y)ya g=f(x); g(y). Biz değiştirecek Yani f=(x,y)=>x+yiçin f=(x)=>(y)=>x+y."
M3D

26

ES6'da bu, yayılma operatörü ile birlikte dinlenme parametreleri kullanılarak kolayca yapılır .

Böylece , sadece argümanların bağlı olması, ancak context ( ) 'in değil, bindArgsbenzer şekilde çalışan bir fonksiyon tanımlayabiliriz .bindthis

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

Ardından, belirli bir işlev foove bir nesne objiçin ifade

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

eşdeğerdir

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

burada yalnızca birinci ve ikinci bağımsız değişkenlere barbağlıyken obj, çağrıda belirtilen bağlam kullanılır ve bağlı bağımsız değişkenlerden sonra fazladan bağımsız değişkenler eklenir. Dönüş değeri basitçe iletilir.


11
es6'yı masaya getiriyor, hala var :(
Daniel Kobe

7
@DanielKobe Teşekkürler! Yayılma operatörleri ve dinlenme parametreleri, Firefox'ta çok önceden uygulanmıştı letve constbunu ilk yayınladığımda henüz mevcut değildi. Şimdi güncellendi.
GOTO 0

Bu harika ve geri arama işlevleri için çalışıyor! Ayrıca bağımsız değişkenleri başa eklemek yerine eklemek için değiştirilebilir.
DenisM

1
Bunun, her bindArgs () çağrıldığında bir kapanma oluşturduğuna dikkat etmek gerekir, bu da performansın yerel bind () işlevini kullanmak kadar iyi olmadığı anlamına gelir.
Joshua Walsh

17

Doğal olarak bindyönteminthis sonucu işlevinde değer kaybolur. Bununla birlikte, bağlam için bağımsız değişken kullanmamak için ortak dolguyu kolayca yeniden kodlayabilirsiniz:

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
Prototipiyle oynamak zorunda olman hoşuma gitmedi Functionama bu bana çok zaman kazandırdı ve bazı çirkin çözümler. Teşekkürler
jackdbernier

3
@jackdbernier: Yapmak zorunda değilsin ama ben bunu bir yöntem olarak daha sezgisel buluyorum bind. Bunu kolayca birfunction bindArgs(fn){ …, args = slice.call(arguments, 1); …}
Bergi

@ Qantas94Heavy: Olmayı önemsediğinizde her zaman yapmanız gereken şey thisbu a. bindTabii bunun yerine kullanabilirsiniz . Oysa OP açıkça gelmez değil umurumda thisdeğeri ve bir ilişkisiz kısmi fonksiyonu istedi.
Bergi

@Bergi: Ayarlamama amacının thisişlevin kullandığı olduğunu düşünüyordum this, ancak o anda onu bağlamak istemediler. Ayrıca, OP'nin sorusundaki //These should both have exact same output.kısım, diğer kısımlarıyla çelişmektedir.
Qantas 94 Heavy

1
@KubaWyrostek: Evet, alt sınıflandırma gibi kısmi yapıcı işlevlerin çalışmasını sağlamak için (bunu tavsiye ettiğimden değil). Bir gerçek bind, kurucularla hiç çalışmaz, bağlı işlevlerin no .prototype. Hayır,Function.prototype !== mymethod.prototype
Bergi

7

Sadece eğlence için küçük bir uygulama daha:

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

Nasıl kullanılır:

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


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

3
Tüm bu gürültülü ve belirsiz cevaplarda bu kesin ve faydalı bir cevaptır. Bunun için +1. Belki cevabın tarzını ormanın o süslü gürültülü geri kalanında çıplak göz için daha çekici hale getirecek şekilde düzeltebilirsiniz. const curry = (fn, ...curryArgs) => (...args) => fn(...curryArgs, args)
Joehannes

2

Nihayetinde ne yapmak istediğinizi tam olarak söylemek biraz zor çünkü örnek keyfi bir örnek, ancak bölümlere (veya körlemelere) bakmak isteyebilirsiniz: 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(){})

John Resig'in makalesine bağlantı: http://ejohn.org/blog/partial-functions-in-javascript/


Bu işlev çalışmıyor, kısmen uygulanan bir işlevi birden fazla çağıramazsınız.
Bergi

Takip ettiğimden emin değilim. Bunu biraz detaylandırabilir veya bir jsbin örneği gönderebilir misiniz?
Kevin Ennis

Bu örneğe bakın . Ayrıca, çalışmak partialiçin undefinedargümanlarla çağrılmanız gerekir - bekleneceği gibi değil ve keyfi sayıda parametre alan işlevlerde bozulma.
Bergi

Kevin, cevabın için teşekkürler. İstediğim şeye yakın ama sanırım thisnesne hala karışıyor. Bir göz atın: jsbin.com/ifoqoj/4/edit
Imbue

Ohhh, gotchya. Bu örnek, aradığınız şeyi çok daha net hale getirir.
Kevin Ennis

2

Eğer başvuru bağlamak istiyorum olabilir bu son ama kodunuzda: -

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

Zaten bu bağlama uygulandı ve daha sonra değiştiremezsiniz. Referansı bunun gibi bir parametre olarak da kullanmanızı önereceğim: -

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

Bir kullanın 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);
}

Temel olarak, param'leri iletmek istediğiniz işlev, bir değişkeni geçirmek için çağırabileceğiniz bir proxy haline gelir ve gerçekten yapmak istediğiniz işlevi döndürür.

Bu yardımcı olur umarım!


1

İsimsiz bir kullanıcı bu ek bilgiyi yayınladı:

Zaten bu yazı içinde sağlanmıştır ne Bina - gördüğüm en zarif çözüm etmektir Curry argümanlarınızı ve bağlam:

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

Verdiğin örnek için, bu iş görür

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

Parametrelerin güvenli bir şekilde muhafaza edilmesini istiyorsanız:

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)

Ancak öyleyse, çözümünüze bağlı kalmalısınız:

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

Daha iyi bir yol.


1

Bu kadar basit?

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

Daha genel çözüm:

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

LoDash kullanarak _.partialişlevi kullanabilirsiniz .

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

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

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

0

Bunu efsane olarak kaydetmek için neden işlevin etrafında bir sarmalayıcı kullanmıyorsunuz?

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, proxy işleviyle tam olarak bu özelliği getirdi.

JQuery 1.9'dan itibaren, bağlam boş veya tanımsız olduğunda, proxy işlevi, proxy ile çağrıldığı gibi bu nesne ile çağrılacaktır. Bu, $ .proxy () işlevinin bağlamı değiştirmeden bir işlevin bağımsız değişkenlerini kısmen uygulamak için kullanılmasına izin verir.

Misal:

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

-5

Jquery kullanım durumu:

yerine:

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

bunu kullan:

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(e){
        var i = this;
        $(e.originalEvent.target).val(i);
    }.bind(i));
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.