Javascript 함수 바인딩

devstone·2021년 12월 5일
4

Javascript Study

목록 보기
4/5
post-thumbnail
post-custom-banner

1. bind 를 쓰게된 배경

1-1 문제상황 : this 정보가 사라져요ㅠ

let user = {
  firstName: "roxie",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(user.sayHi, 1000); // Hello, undefined!
  • setTimeout에 객체에서 분리된 함수인 user.sayHi가 들어가면서 this 정보를 잃어버림. 객체 메서드를 꺼내서 쓸 때 컨텍스트를 잃어버리는 것이 문제 (이 예시에서는 user컨텍스트를 잃어버린 것)
  • 컨텍스트란?

1-2. 해결책

🥝 래퍼 함수 이용

  • 외부 렉시컬 환경에서 아까 잃었던 객체의 정보를 받아 보통 때처럼 메서드를 호출
  • 부르고자 하는 객체 내 메서드를 함수로 한 겹 감싼다.
let user = {
  firstName: "roxie",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(function() {
  user.sayHi(); // Hello, roxie!
}, 1000);
  • 외부 렉시컬 환경에서 user를 받아서 보통 때처럼 메서드 호출
  • 이렇게 되면 함수 고유의 렉시컬 환경이 아닌 외부의 렉시컬 환경을 참조하게 되므로 setTimeout이 트리거 되기 이전에 user가 변경되면 변경 내역이 반영됨 (함수는 실행될 때 렉시컬 환경이 구성되므로)
let user = {
  firstName: "roxie",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(() => user.sayHi(), 1000);

// 1초가 지나기 전에 user의 값이 바뀜
user = { sayHi() { alert("또 다른 사용자!"); } };

// setTimeout에 또 다른 사용자!가 뜸 
  • 이러한 취약성을 bind를 통해 해결할 수 있다 !

2. bind로 this 수정하기

  • 기본 문법
    • 이처럼 bind하면 호출 가능한 특수 객체가 생성됨
    • 이 특수객체는 context를 this로 고정한 함수를 반환함
    let bound = func.bind(context, [arg1], [arg2], ...);
  • 위의 예시 수정해보기
    let user = {
      firstName: "roxie",
      sayHi() {
        alert(`Hello, ${this.firstName}!`);
      }
    };
    
    // user객체의 context(실행환경)을 갖는 함수를 반환하는 특수객체를 sayHi에 넣음
    let sayHi = user.sayHi.bind(user); // (*)
    
    // 이제 객체 없이도 객체 메서드를 호출할 수 있습니다.
    sayHi(); // Hello, roxie!
    
    setTimeout(sayHi, 1000); // Hello, roxie!
    
    // 1초 이내에 user 값이 변화해도
    // sayHi는 기존 값을 사용합니다.
    user = {
      sayHi() { alert("또 다른 사용자!"); }
    };
  • 인수는 그대로 전달되고 bind에 의해 this만 고정됨
    let user = {
      firstName: "roxie",
      say(phrase) {
        alert(`${phrase}, ${this.firstName}!`);
      }
    };
    
    let say = user.say.bind(user);
    
    say("Hello"); // Hello, roxie (인수 "Hello"가 say로 전달되었습니다.)
    say("Bye"); // Bye, roxie ("Bye"가 say로 전달되었습니다.)

+) bindAll로 메서드 전체 바인딩 가능

  • lodash라이브러리의 _.bindAll
  • 아니면 반복문을 통해 메서드 바인딩 와랄라 가능!

3. bind 확장

인수의 바인딩

  • 컨텍스트 뿐만 아니라 함수의 인수도 bind를 통해 고정시킬 수 있다
  • 기본문법
    let bound = func.bind(context, [arg1], [arg2], ...);
  • 예시
    function mul(a, b) {
      return a * b;
    }
    
    let double = mul.bind(null, 2);
    
    alert( double(3) ); // = mul(2, 3) = 6
    alert( double(4) ); // = mul(2, 4) = 8
    alert( double(5) ); // = mul(2, 5) = 10
    
    let triple = mul.bind(null, 3);
    
    alert( triple(3) ); // = mul(3, 3) = 9
    alert( triple(4) ); // = mul(3, 4) = 12
    alert( triple(5) ); // = mul(3, 5) = 15
  • double 에는 컨텍스트가 null, 첫번째 인수는 2가 디폴트로 들어감
  • 부분 적용 이라고 부름 → 직관적인 이름의 독립함수를 만들 수 있다는 장점이 있음
  • 응용
    • send(from, to, text)가 있다고 가정해 봅시다. 객체 user 안에서 부분 적용을 활용하면, 전송 주체가 현재 사용자인 함수 sendTo(to, text)를 구현할 수 있다!
    • 근데,, 컨텍스트 고정 안 시킬건데,, null 하나하나 써주기 귀찮다면 . . . ?

컨텍스트는 고정시키지 않는 bind 구현

  • 만약 인수 일부는 고정하되 this의 배경이 되는 컨텍스트는 고정하고 싶지 않다면 ? → 네이티브 bind만으로는 컨텍스트 생략하고 인수로 바로 뛰어넘지는 못함.
  • 헬퍼함수 partial를 통해 구현 가능
    // 인수만 바인딩해주는 헬퍼함수 
    function partial(func, ...argsBound) {
      return function(...args) { // (*)
        return func.call(this, ...argsBound, ...args);
      }
    }
    
    // 사용법:
    let user = {
      firstName: "John",
      say(time, phrase) {
        alert(`[${time}] ${this.firstName}: ${phrase}!`);
      }
    };
    
    // 시간을 고정한 부분 메서드를 추가함
    user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());
    
    user.sayNow("Hello");
    // 출력값 예시:
    // [10:00] John: Hello!

partial(func[, arg1, arg2...])을 호출하면 래퍼((*))가 반환됩니다. 래퍼를 호출하면 func이 다음과 같은 방식으로 동작합니다.

  • 동일한 this를 받습니다(user.sayNow는 user를 대상으로 호출됩니다).
  • partial을 호출할 때 받은 인수("10:00")는 ...argsBound에 전달됩니다.
  • 래퍼에 전달된 인수("Hello")는 ...args가 됩니다.
  • lodash의 _.partial를 사용하면 직접 구현하지 않아도 사용할 수 있음 !

call, apply와의 차이점

  • call, apply는 this를 고정시켜 함수를 호출해주는 반면(바로 실행), bind는 this가 고정된 새로운 함수를 return 해준다.

4. 주의사항

  • 한 번 bind를 적용한 함수의 컨텍스트, 인수는 완전히 고정되고 바꿀 수 없다! → bind를 통해 생성된 특수 객체 함수의 생성 시점의 컨텍스트만와 인수(제공된 경우에)만을 기억한다

레퍼런스

모던 자바스크립트

profile
개발하는 돌멩이
post-custom-banner

3개의 댓글

comment-user-thumbnail
2021년 12월 9일

궁금한 점이 있는데, 문제상황에서 user object의 sayHi 메서드를 호출할 때, setTimeout(user.sayHi(), 1000);하게 되면 정상적으로 출력되지 않나요?

1개의 답글