1. bind
를 쓰게된 배경
1-1 문제상황 : this 정보가 사라져요ㅠ
let user = {
firstName: "roxie",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000);
- setTimeout에 객체에서 분리된 함수인
user.sayHi
가 들어가면서 this 정보를 잃어버림. 객체 메서드를 꺼내서 쓸 때 컨텍스트를 잃어버리는 것이 문제 (이 예시에서는 user컨텍스트를 잃어버린 것)
- 컨텍스트란?
1-2. 해결책
🥝 래퍼 함수 이용
- 외부 렉시컬 환경에서 아까 잃었던 객체의 정보를 받아 보통 때처럼 메서드를 호출
- 부르고자 하는 객체 내 메서드를 함수로 한 겹 감싼다.
let user = {
firstName: "roxie",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi();
}, 1000);
- 외부 렉시컬 환경에서 user를 받아서 보통 때처럼 메서드 호출
- 이렇게 되면 함수 고유의 렉시컬 환경이 아닌 외부의 렉시컬 환경을 참조하게 되므로 setTimeout이 트리거 되기 이전에 user가 변경되면 변경 내역이 반영됨 (함수는 실행될 때 렉시컬 환경이 구성되므로)
let user = {
firstName: "roxie",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(() => user.sayHi(), 1000);
user = { sayHi() { alert("또 다른 사용자!"); } };
- 이러한 취약성을
bind
를 통해 해결할 수 있다 !
2. bind로 this 수정하기
- 기본 문법
- 이처럼 bind하면 호출 가능한 특수 객체가 생성됨
- 이 특수객체는 context를 this로 고정한 함수를 반환함
let bound = func.bind(context, [arg1], [arg2], ...);
let user = {
firstName: "roxie",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user);
sayHi();
setTimeout(sayHi, 1000);
user = {
sayHi() { alert("또 다른 사용자!"); }
};
- 인수는 그대로 전달되고 bind에 의해 this만 고정됨
let user = {
firstName: "roxie",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
let say = user.say.bind(user);
say("Hello");
say("Bye");
+) 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) );
alert( double(4) );
alert( double(5) );
let triple = mul.bind(null, 3);
alert( triple(3) );
alert( triple(4) );
alert( triple(5) );
- 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");
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를 통해 생성된 특수 객체 함수의 생성 시점의 컨텍스트만와 인수(제공된 경우에)만을 기억한다
레퍼런스
모던 자바스크립트
궁금한 점이 있는데, 문제상황에서 user object의 sayHi 메서드를 호출할 때, setTimeout(user.sayHi(), 1000);하게 되면 정상적으로 출력되지 않나요?