[TIL] 24/04/26 apply, call, bind을 사용하여 this 바인딩

this를 명시적으로 바꿔주는 메소드를 알아보자

 

apply, call유사 배열 객체에 배열 메소드를 사용하는 용도로 많이 쓴다

ex) arguments, NodeList, HTMLCollections

 

bind는 영구적으로 this를 바꾼 새로운 함수를 반환한다

메소드 내부 중첩 함수, 콜백함수에서 this 불일치 문제를 해결하는 용도로 많이 쓴다

 

call, apply, bind 형태

call

func.call(thisArg[, arg1, arg2, ...args])

apply와 같지만 

두번째 인자에 ,로 구분된 리스트 형태로 전달한다

 

apply

func.apply(thisArg [,argusArray])

첫번째 인자로 this로 사용할 요소, 

두번째 인자로 함수에 전달할 요소를 배열로 전달한다 (array = apply로 외우면 된다 👍)

 

bind

func.bind(thisArg [,arg1, arg2, ...args])()

bind는 call과 형태가 동일하다

하지만 새로운 함수를 반환하기 때문에 명시적으로 호출이 필요하다

 

 

call, apply 사용 예시

배열 고차 함수는 배열에서 호출하므로(ex Arr.forEach) this는 메소드를 호출한 배열이 된다

하지만 유사 배열 객체는 배열이 아니니 배열 메소드를 못쓴다!

 

이번에 공부를 하게 된 계기는 elements를 가져왔을 때 forEach문이 안돌아져서,,ㅋㅋ

NodeList가 유사 배열 객체인 것도 까먹고 있었다..

그리고 배열 고차함수에서 3번째 인자로 this 전달되지 않나? 굳이 따로 바인딩을 해야하나? 라는 의문으로 시작

 

 

그래서!

NodeList는 이렇게 배열 고차함수를 쓰면된다!

const elements = document.querySelectorAll('.hideable'); // NodeList 반환 (유사배열객체)

Array.prototype.forEach.call(elements, function(element) {
    element.style.display = 'none';
});

 

call을 사용하는 예시로

function sum() {
  // arguments 객체를 배열로 변환하여 reduce 메소드를 사용하여 합을 계산
  return Array.prototype.reduce.call(arguments, function(acc, curr) {
      return acc + curr;
  }, 10); //초기값 10 전달
}

console.log(sum(1, 2, 3, 4, 5)); // 25

 

같은 코드를 apply로 작성하면

이것도 신기했다 

reduce에서 사용할 함수를 배열에 담아서 전달

function sum() {
  return Array.prototype.reduce.apply(arguments, [function(acc, curr) {
      return acc + curr;
  }, 10]); //초기값 10 전달
}

console.log(sum(1, 2, 3, 4, 5)); // 25

 

아님 함수를 따로 빼서 아예 함수를 전달해도 된다

function getSum(acc, curr) {
  return acc + curr;
}

function sum() {
  return Array.prototype.reduce.call(arguments, getSum, 10); // call
  // return Array.prototype.reduce.apply(arguments, [getSum]); // apply
}

console.log(sum(1, 2, 3, 4, 5)); // 25

 

 

bind 사용 예시

unboundGetX는 메소드를 할당하였는데, 이러면 기존 객체와의 연결이 끊어져 this가 전역 객체를 가르키게 된다

boundGetX는 unboundGetX에 obj를 this로 바인딩한 함수이다

const obj = {
  x: 42,
  getX: function() {
    console.log(this)
    return this.x;
  }
};

const unboundGetX = obj.getX; // 메소드를 다른 변수에 할당
// console.log(unboundGetX()); // window, undefined

const boundGetX = unboundGetX.bind(obj); 
// console.log(boundGetX()); // obj, 42

 

해당 메소드가 호출될 때 this를 예측하기 위해 메소드를 분리하는 게 좋다고 한다 
특히 비동기적인 작업이 일어나는 콜백 함수에서는 원래 객체와의 연결을 보장하기 위해 분리한다고 한다

 

아래 예시처럼 obj.greet 메소드를 분리하여

bind를 통해 this를 기존 obj로 연결하여 전달한다

const obj = {
  name: 'John',
  greet: function() {
    console.log(`Hello, ${this.name}!`);
  }
};

const handler = obj.greet; // obj와 연결이 끊김
document.querySelector('button').addEventListener('click', handler.bind(obj));

 

 

추가로 배열 고차 함수에서 this 전달

이전에 언급한 것과 같이 배열 고차 함수는 배열에서 호출하므로(ex Arr.forEach) this는 메소드를 호출한 배열이 된다

하지만 3번째 인수로 this를 명시적으로 전달할 수 있다

const fruits = [
  { name: 'apple', color: 'red' },
  { name: 'banana', color: 'yellow' },
  { name: 'orange', color: 'orange' }
];

const fruitNames = fruits.map(function(fruit) {
  return this.prefix + ' ' + fruit.name; // 여기서 this는 thisArg로 지정한 { prefix: 'My favorite' }
}, { prefix: 'My favorite' });


//["My favorite apple", "My favorite banana", "My favorite orange"]
console.log(fruitNames);

 

결론

고차 함수를 직접 호출 할 수 있을 때(배열)만 세번째 인수 전달이 가능하고,

유사 배열 객체와 같이 배열이 아니면 call, apply, this 바인딩을 해야한다

 

꺄 주말이다~~