apply, call, bind 함수 모두 this를 명시적으로 바인딩하는 함수이지만, 함수 호출 시점과 인자 전달 방식에서 차이점이 있다.
## 요약
| 메서드 | 동작 | 인자 전달 방식 |
| ----- | ------------------------- | -------------- |
| call | this를 바인딩하고 **함수를 즉시 호출** | 개별 전달 (콤마로 나열) |
| apply | this를 바인딩하고 함수를 즉시 호출 | 배열로 전달 |
| bind | this를 바인딩한 **새로운 함수를 반환** | 개별 전달 (콤마로 나열) |
## call
```javascript
func.call(thisArg, arg1, arg2, ...argN)
```
this를 바인딩하고 **그 함수를 즉시 호출**한다.
함수의 인자로 전달할 값들은 this에 바인딩 할 객체 다음에 콤마로 나열해서 개별로 전달한다.
### 활용
- 다른 객체의 메서드를 재활용하는 경우
## apply
```javascript
func.apply(thisArg, [argsArray])
```
call과 동일하게 this를 바인딩하고 **그 함수를 즉시 호출**한다.
함수의 인자로 전달할 값들은 this에 바인딩 할 객체 다음에 배열로 전달한다.
### 활용
- 배열을 개별 인수로 펼쳐야 하는 경우 (spread 연산자와 비슷..)
```javascript
const numbers = [5, 2, 8, 1, 9];
// Math.max는 배열이 아닌 개별 인수를 받으므로 apply 사용
const max = Math.max.apply(null, numbers);
```
## bind
```javascript
const boundFunc = func.bind(thisArg, arg1, arg2, ...argN)
```
bind는 함수에 지정한 this를 바인딩한 **새로운 함수를 반환**한다.
함수의 인자로 전달할 값들은 this에 바인딩 할 객체 다음에 개별로 전달한다.
---
bind 시점에 일부 인자를 고정할 수 있다.
bind 시점에 전달한 인수와 호출 시점에 전달한 인수는 차례대로 결합되어 원본 함수에 전달된다.
```javascript
function introduce(name, age, city, hobby, ...rest) {
console.log(`이름: ${name}`);
console.log(`나이: ${age}`);
console.log(`도시: ${city}`);
console.log(`취미: ${hobby}`);
console.log(`추가 정보:`, rest);
console.log(`this 컨텍스트:`, this.role);
}
const context = { role: '개발자' };
// bind 시점에 일부 인수 고정
const introduceCheolsoo = introduce.bind(context, '김철수', 25);
// 호출 시점에 나머지 인수 전달
introduceCheolsoo('서울', '프로그래밍', '독서', '영화감상', '운동');
/* 출력:
이름: 김철수 (bind에서 고정)
나이: 25 (bind에서 고정)
도시: 서울 (호출 시 전달)
취미: 프로그래밍 (호출 시 전달)
추가 정보: ['독서', '영화감상', '운동'] (나머지 매개변수)
this 컨텍스트: 개발자
*/
```
**bind한 함수를 다시 재바인딩하는 경우에 인수는 순서대로 결합되어 전달**된다.
```javascript
function combine(a, b, c, d) {
console.log(`a: ${a}, b: ${b}, c: ${c}, d: ${d}`);
return this.name;
}
const obj = { name: '바인드객체' };
const boundOnce = combine.bind(obj, '첫번째');
const boundTwice = boundOnce.bind({ name: '무시됨' }, '두번째');
boundTwice('세번째', '네번째');
// 출력: a: 첫번째, b: 두번째, c: 세번째, d: 네번째
// 반환: "바인드객체" (this는 여전히 첫 번째 바인드 객체)
```
---
bind로 바인딩한 this는 **대부분의 경우 변경되지 않는다.**
이는 `bind`가 내부적으로 클로저를 사용하여 `this` 값을 고정하기 때문.
```javascript
const obj1 = { name: '객체1' };
const obj2 = { name: '객체2' };
const obj3 = { name: '객체3' };
function getName() {
return this.name;
}
const boundToObj1 = getName.bind(obj1);
// 다시 bind를 시도해도 this는 여전히 obj1
const reboundToObj2 = boundToObj1.bind(obj2);
console.log(reboundToObj2()); // "객체1" (obj2가 아님!)
// call/apply로도 this를 변경할 수 없음
console.log(boundToObj1.call(obj3)); // "객체1" (obj3가 아님!)
console.log(boundToObj1.apply(obj3)); // "객체1" (obj3가 아님!)
```
하지만 **new 연산자를 사용하는 경우에는 덮어씌워진다**. (this 결정에 가장 높은 우선순위를 가지고 있기 때문이다.)
```javascript
function Person(name) {
this.name = name;
}
const BoundPerson = Person.bind({name: "Bob"});
const p = new BoundPerson("Alice");
console.log(p.name); // "Alice"
```
---
함수를 감싸는 래퍼를 만든 것이므로 **실행 순서는 원본 함수와 동일한 흐름을 갖는다.**
```javascript
function outerFunction() {
console.log('1. 외부 함수 시작');
function innerFunction(message) {
console.log('3. 내부 함수 실행:', message);
console.log('4. this는:', this.name);
}
const obj = { name: '바인드객체' };
const boundInner = innerFunction.bind(obj, '바인드된 메시지');
console.log('2. bind 완료, 함수 호출 시작');
boundInner();
console.log('5. 외부 함수 종료');
}
outerFunction();
```
### 활용
- 이벤트 핸들러 같은 콜백 함수에서 this를 명시적으로 지정할때