함수 바인딩

setTimeout에 메서드를 전달할 때처럼, 객체 메서드를 콜백으로 전달할 때는 this가 사라지는 문제가 생긴다.

사라진 this

앞서 다양한 예제를 통해 this 정보가 사라지는 문제를 경험했다. 객체 메서드가 객체 내부가 아닌 다른 곳에 전달되어 호출되면 this가 사라진다.

대표적인 예는 setTimeout등에서 콜백함수로 넘겨지는 경우에 발생하는 것이다.

let user = {
  firstName: 'John',
  sayHi() {
    alert(`Hello, {this.firstName}!`);
  },
};

setTimeout(user.sayHi, 1000); // Hello, undefined!

이런 문제는 콜백함수를 전달 할 때, 객체에서 메서드가 분리(user.sayHi)되어 하나의 함수로써 전달되기 때문이다.

해결 1 : 래퍼(Wrapper) 함수

가장 간단한 해결책은 래퍼 함수를 사용하는 것이다.

let user = {
  firstName: 'John',
  sayHi() {
    alert(`Hello, {this.firstName}!`);
  },
};

setTimeout(function () {
  user.sayHi(); // Hello, John!
}, 1000);

위 예시가 의도대로 동작하는 이유는, 외부 렉시컬 환경에서 user를 받아 보통 때와 똑같이 메서드를 호출하기 때문이다.

단, 이 경우 약간의 취약성이 생기는데, setTimeout이 트리거 되기 전, user에 변경이 가해지면, 변경된 상태의 객체 메서드를 호출한다는 점이다.

let user = {
  firstName: 'John',
  sayHi() {
    alert(`Hello, {this.firstName}!`);
  },
};

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

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

// setTimeout에 또 다른 사용자!

이런 문제는 두 번째 방법을 사용함으로써 방지할 수 있다.

방법 2 : bind

모든 함수는 this를 수정하게 해주는 내장 메서드 bind를 제공한다.

기본 문법은 다음과 같다.

// 더 복잡한 문법은 뒤에 나옵니다.
let boundFunc = func.bind(context);

func.bind(context)는 함수처럼 호출 가능한 '특수 객체(exotic object)'를 반환한다. 이 객체를 호출하면 thiscontext로 고정된 함수 func이 반환된다.

이제 boundFunc를 호출하면 this가 고정된 func를 호출하는 것과 동일한 효과를 본다.

아래 funcUser에는 thisuser로 고정된 func이 할당된다.

이제 객체 메서드에 bind를 적용해보자.

let user = {
  firstName: 'John',
  sayHi() {
    alert(`Hello, {this.firstName}!`);
  },
};

let sayHi = user.sayHi.bind(user); // (*)

// 이제 객체 없이도 객체 메서드를 호출할 수 있습니다.
sayHi(); // Hello, John!

setTimeout(sayHi, 1000); // Hello, John!

// 1초 이내에 user 값이 변화해도
// sayHi는 기존 값을 사용합니다.
user = {
  sayHi() {
    alert('또 다른 사용자!');
  },
};

부분 적용 (Partial Application)

지금껏 this에 대해서만 이야기했지만, this뿐만 아니라 인수에 대해서도 바인딩이 가능하다. 이는 자주 쓰이진 않지만 가끔 유용하다.

```bind`의 전체 문법은 다음과 같다.

let bound = func.bind(context, [arg1], [arg2], ...);

bind는 컨텍스트를 this로 고정하는 것 뿐만 아니라 함수의 인수도 고정해준다.

곱셈을 해주는 함수 mul(a, b)를 예시로 들어보고, bind를 통해 새로운 함수 double을 만들어본다.

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

이처럼, context는 따로 넘겨주지 않고 인수에 대해서만 값을 전달해 고정해주는 방식을 **부분 적용(Partial Application)**이라고 한다.

이는 매우 포괄적인 함수를 기반으로 덜 포괄적인 변형 함수를 만들어 낼 수 있다는 점에서 유용하다.