바인딩하지 않고 함수 인수를 바인딩하는 방법은 무엇입니까?


115

Javascript에서 this매개 변수 를 바인딩하지 않고 인수를 함수에 바인딩하는 방법은 무엇입니까?

예를 들면 :

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

함수의 범위 (예 : 설정 this= null) 를 바인딩해야하는 부작용을 어떻게 피할 수 있습니까?

편집하다:

혼란을 드려 죄송합니다. 인수를 바인딩 한 다음 나중에 바인딩 된 함수를 호출하고 마치 원래 함수를 호출하고 바인딩 된 인수를 전달한 것처럼 정확하게 동작하도록 할 수 있습니다.

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?

나는 이것에 아직 새롭다. 혼란을 끼쳐서 미안하다.

감사!


1
다시 바인딩하는 것이 부적절 this합니까? var b = c.bind(this, 1,2,3);
kojiro

왜의 첫 번째 값이 할 문제가 bind()BE를 null? FF에서 잘 작동하는 것 같습니다.
Russ

7
함수에 이미이 바인딩이있는 경우 bind의 첫 번째 인수로 null을 사용하면 문제가 발생합니다 (즉, 개체 메서드를 전역 범위에 바인딩).
Imbue

1
당신이 뭘 하려는지 정말 잘 모르겠어요. thisJavaScript에서 완전히 바인딩을 해제 할 수는 없습니다 . 그것은 항상 무언가를 의미 합니다 . 바인딩 그래서 this받는 this둘러싸 기능의 많은 이해된다.
kojiro

나는 JS에서 꽤 새롭다. 더 명확하게 질문을 편집했습니다. 내가 원하는 것이 불가능할 수도 있습니다. 감사.
Imbue dec.

답변:


32

이 작업을 수행 할 수 있지만 "이"값을 설정하는 데 사용되는 용어이므로 "바인딩"으로 생각하지 않는 것이 가장 좋습니다. 인수를 함수로 "래핑"하는 것으로 생각할 수 있습니까?

당신이하는 일은 클로저를 통해 원하는 인수가 내장 된 함수를 만드는 것입니다.

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

바로 거기에 구문이 있기를 바랍니다. 그것이하는 일은 언제 어디서나 호출 할 수있는 withWrappedArguments ()라는 함수를 만드는 것입니다. 그곳에. 원하는 경우 호출시 추가 인수를 허용 할 수도 있습니다. 비밀은 마지막 닫는 중괄호 뒤의 괄호입니다. 이렇게하면 전달 된 값으로 외부 함수가 즉시 실행되고 나중에 호출 할 수있는 내부 함수가 생성됩니다. 전달 된 값은 함수가 생성 될 때 고정됩니다.

이것은 bind가하는 일입니다. 그러나 이렇게하면 래핑 된 인수가 단순히 지역 변수에 대한 클로저이며이 동작을 변경할 필요가 없습니다.


16
일반적으로 "Currying"이라고합니다.
M3D

@ M3D 자본화가 너무 이상해 보입니다.
Andrew

사용 방법에 대한 예제가 제공되면 훨씬 더 도움이 될 것입니다.
Andrew

en.wikipedia.org/wiki/Currying 링크는 관심있는 모든 사람을위한 링크입니다. TLDR 버전은 "대신 f(x,y)우리가 원 f(x)(y)하거나 g=f(x); g(y). 그래서 우리는로 변경 f=(x,y)=>x+y합니다 f=(x)=>(y)=>x+y."
M3D

26

ES6에서는 스프레드 연산자 와 함께 나머지 매개 변수 를 사용하여 쉽게 수행 할 수 있습니다 .

따라서 우리는 인수 만 바인딩되고 컨텍스트 ( )가 아니라는 점을 제외하고 bindArgs와 같이 작동 하는 함수 를 정의 할 수 있습니다 .bindthis

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

그런 다음 지정된 함수 foo및 객체 obj에 대해 문

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

다음과 같다

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

첫 번째 및 두 번째 인수 만에 바인딩되는 bar반면 obj호출에 지정된 컨텍스트 가 사용되고 바인딩 된 인수 뒤에 추가 인수가 추가됩니다. 반환 값은 단순히 전달됩니다.


11
테이블에 ES6을 제공, 여전히 :( VAR를 사용
다니엘 고베

7
@DanielKobe 감사합니다! 확산 사업자 및 휴식 PARAMS은 오래 전에 파이어 폭스에서 구현되었습니다 letconst사용할 수 없습니다 된 아직 시간 내가 처음이를 기록했다. 지금 업데이트되었습니다.
GOTO 0

이것은 훌륭하고 콜백 함수에서 작동합니다! 인수를 앞에 추가하지 않고 추가하도록 변경할 수도 있습니다.
DenisM

1
이것은 bindArgs ()가 호출 될 때마다 클로저를 생성한다는 점에 주목할 가치가 있습니다. 이는 성능이 기본 bind () 기능을 사용하는 것만 큼 좋지 않음을 의미합니다.
Joshua Walsh

17

네이티브 bind메서드 에서는 this결과 함수 의 값이 손실됩니다. 그러나 컨텍스트에 대한 인수를 사용하지 않도록 공통 shim을 쉽게 다시 코딩 할 수 있습니다.

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
의 프로토 타입을 가지고 플레이해야한다는 사실이 마음에 들지 Function않지만 많은 시간과 추악한 해결 방법을 절약했습니다. 감사합니다
jackdbernier 2013 년

3
@jackdbernier : 그럴 필요는 없지만, bind. 쉽게 변환 할 수 있습니다function bindArgs(fn){ …, args = slice.call(arguments, 1); …}
Bergi

@ Qantas94Heavy : 그게 당신이 관심을 가질 때 항상해야하는 thisa입니다. bind물론 대신 사용할 수 있습니다 . 그러나 OP는 값 에 대해 명시 적으로 신경 쓰지 않으며this 바인딩되지 않은 부분 함수를 원했습니다.
Bergi 2014-06-12

@Bergi : 설정하지 않은 목적은 this함수 가을 사용하는 것이라고 가정 this했지만 그 순간에는 바인딩하고 싶지 않았습니다. 또한 OP 질문의 비트는 //These should both have exact same output.다른 부분과 모순됩니다.
콴타스 94 헤비

1
@KubaWyrostek : 예, 부분 생성자 함수를 서브 클래 싱처럼 작동하도록 만드는 것입니다 (하지만 권장하지는 않습니다). 실제 bind는 생성자와 전혀 작동하지 않으며 바인딩 된 함수에는 .prototype. 아니오,Function.prototype !== mymethod.prototype
Bergi 2014-08-22

7

재미를위한 작은 구현이 하나 더 있습니다.

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

사용하는 방법:

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

apply ()를 호출 할 때 "null"을 전달하는 것이 더 정확할 것 같습니다
eddiewould

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

3
이 모든 시끄럽고 모호한 답변에서 이것은 정확하고 유용한 답변입니다. 따라서 +1. 정글의 멋진 시끄러운 나머지 부분에서 육안으로 더 매력적으로 보이도록 대답의 스타일을 다듬을 수 있습니다. const curry = (fn, ...curryArgs) => (...args) => fn(...curryArgs, args)
Joehannes

2

예제가 임의적이기 때문에 궁극적으로 무엇을하고 싶은지 정확히 말하기는 조금 어렵지만 부분 (또는 커링)을 살펴보고 싶을 수도 있습니다 : 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의 기사 링크 : http://ejohn.org/blog/partial-functions-in-javascript/


이 함수는 작동하지 않으며 부분적으로 적용된 함수를 두 번 이상 호출 할 수 없습니다.
Bergi

내가 따르는 지 모르겠다. 그것에 대해 조금 더 자세히 설명하거나 jsbin 예제를 게시 할 수 있습니까?
Kevin Ennis

이 예를 참조하십시오 . 또한 작동하려면 인수를 사용하여 partial호출해야합니다. undefined예상되는 것이 아니라 임의의 수의 매개 변수를 사용하는 함수에서 중단됩니다.
Bergi

Kevin, 답변 해 주셔서 감사합니다. 내가 원하는 것에 가깝지만 this여전히 물건이 엉망인 것 같습니다. : 한 번 봐 가지고 jsbin.com/ifoqoj/4/edit
스며들게

오, 고치 야. 이 예제는 당신이 찾고있는 것을 훨씬 더 명확하게합니다.
Kevin Ennis 2012

2

마지막 으로 이것 에 대한 참조를 바인딩하고 싶을 수도 있지만 코드는 다음과 같습니다.

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

이미 예를 들어 바인딩 적용 나중에 변경할 수 없습니다. 다음과 같은 매개 변수로 참조를 사용하는 것이 좋습니다.

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

를 사용 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);
}

기본적으로 매개 변수를 전달하려는 함수는 변수를 전달하기 위해 호출 할 수있는 프록시가되고 실제로 수행하려는 함수를 반환합니다.

도움이 되었기를 바랍니다!


1

익명의 사용자가 다음 추가 정보를 게시했습니다.

이 게시물에서 이미 제공 한 내용을 기반으로합니다. 내가 본 가장 우아한 솔루션은 Curry 의 주장과 맥락입니다.

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

당신이 준 예를 들어, 이것은 할 것입니다

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

매개 변수의 포함을 보장하려는 경우 :

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)

그러나 그렇다면 솔루션을 고수해야합니다.

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

더 나은 방법입니다.


1

그렇게 간단합니까?

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

보다 일반적인 솔루션 :

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 것은 당신이 사용할 수있는 _.partial기능.

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

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

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

0

함수 주위에 래퍼를 사용하여 이것을 mythis로 저장하지 않는 이유는 무엇입니까?

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는 프록시 기능으로 정확히 그 기능을 가져 왔습니다.

jQuery 1.9부터 컨텍스트가 null이거나 정의되지 않은 경우 프록시가 호출 된 것과 동일한이 개체를 사용하여 프록시 함수가 호출됩니다. 이렇게하면 컨텍스트를 변경하지 않고 함수의 인수를 부분적으로 적용하는 데 $ .proxy ()를 사용할 수 있습니다.

예:

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

-5

Jquery 사용 사례 :

대신 :

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

이것을 사용하십시오 :

for(var i = 0;i<3;i++){
    $('<input>').appendTo('body').click(function(e){
        var i = this;
        $(e.originalEvent.target).val(i);
    }.bind(i));
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.