본문 바로가기
js 이론

call, apply, bind

by 김홍중 2021. 8. 25.

기본 binding

자바스크립트 this binding이 헷갈려서 정리하게 되었습니다. 먼저 기본적인 binding은 어떤식으로 이뤄지는지 살펴보겠습니다.

const introduce = function() {
  console.log(this);
  const msg = `Hello! my name is ${this.name}`;
  console.log(msg);
};

introduce();

현재 this는 전역객체에 해당하는 window객체이고 name에 관해 값을 할당한것이 없어서 name은 undefined여서 아무것도 출력하지 않습니다.

위 코드에서 var name = 'basic binding'만 추가하여 값을 할당해보겠습니다.

const introduce = function() {
  console.log(this);
  const msg = `Hello! my name is ${this.name}`;
  console.log(msg);
};

var name = 'basic binding'
introduce();

 

basic binding을 출력함을 볼 수 있습니다. 전역객체에 컨텍스트가 바인딩 된 상태인데 이 name에 값을 할당하였기 때문입니다.

만약 name을 var가 아니라 const나 let으로 선언하면 어떤 결과를 출력할까요?

window를 출력하고 두번째에서는 name is 이후에 아무것도 출력하지 않아서 undefined임을 알 수 있습니다.

이 부분은 왜 그렇게 나오는지 아직 파악하지 못하여 추후 알아봐야할 사항입니다.

 

명시적 바인딩 - call, apply, bind

특정한 객체를 컨텍스트로 반영하는 의도를 나타내는 방법은 자바스트립트의 내장함수 call, apply, bind를 이용하는것입니다.

 

call과 apply

const person = {
  name: 'Hong Jung',
};

const introduceMore = function(fruit) {
    const msg = `Hello, my name is ${this.name}, and I like ${fruit}`;
    console.log(msg);
  };
  
const fruit = 'apple';
introduceMore(fruit);
introduceMore.call(person, fruit);
introduceMore.apply(person, [fruit]);

call과 apply모두 실행하는 시점에 introduceMoreArrow의 arguments와 caller가 null이 아니므로 introduceMoreArrow함수를 실행하는것으로 판단하였습니다. 

그리고 첫번째 인자에 this에 반영하고 싶은 객체를 넘겨주어 this를 바꾸고나서 실행합니다.

이는 출력값으로 확인할 수 있는데,

첫번째 introduceMore함수의 경우 this에 반영된 객체가 없어서 window객체이기 때문에 아무것도 출력되지 않습니다.

두번째와 세번째에 각각 call, apply하는 경우에는 this에 person로 반영하였기 때문에 person객체의 name을 출력합니다.

여기서 call과 다르게 apply함수는 두번째 인자에 배열에 담아 넘겨주어야합니다.

arrow function(화살표 함수)에서 this

const person = {
  name: 'Hong Jung',
};

const introduceMoreArrow = (fruit) => {
  const msg = `Hello, my name is ${this.name}, and I like ${fruit}`;
  console.log(msg);
};

const fruit = 'apple';
introduceMoreArrow(fruit);
introduceMoreArrow.call(person, fruit);
introduceMoreArrow.apply(person, [fruit]);

출력되는 값이 아예 없습니다. 어떻게 된것일까요? 모두가 전역객체인 winodw에 접근하는것일까요?

이를 확인하기 위하여 이전 코드에서 this를 출력하는 코드로 변경해보겠습니다.

const person = {
  name: 'Hong Jung',
};

const introduceMoreArrow = (fruit) => {
  console.log(this);
};

const fruit = 'apple';
introduceMoreArrow(fruit);
introduceMoreArrow.call(person, fruit);
introduceMoreArrow.apply(person, [fruit]);

예상과 같이 전역객체 window를 출력합니다.

 

MDN에서 그 이유를 찾을 수 있습니다. 화살표 함수는 자신의 this가 없습니다. 대신 둘러싸는 lexical scope(렉시컬 범위)의 this가 사용됩니다.

이전에 글로벌 실행컨텍스트(GEC)와 introduceMoreArrow EC가 push되어 있는 상태입니다. 여기서 intoduceMoreArrow 범위에서 this를 찾지 못하여 introduceMoreArrow를 pop합니다. 그러면 GEC만 남겨됩니다. GEC에서 다시 this를 찾고 그 범위에서 window전역객체를 찾은것입니다.

 

bind

const person = {
    name: 'Hong Jung',
};

const introduceMore = function(fruit) {
    const msg = `Hello, my name is ${this.name}, and I like ${fruit}`;
    console.log(msg);
};

const fruit = 'apple';
const boundIntroduceMore = introduceMore.bind(person);
boundIntroduceMore(fruit);

MDN 을 참고하면 bind함수는 지정한 this값과 초기 arguments를 사용하여 변경한 원본 함수의 복제본을 리턴함을 알 수 있습니다. 이 리턴된 함수(boundIntroduceMore)는 this를 지정한 객체를 갖고 있어서 나중에 사용할 수 있습니다. 그래서 이름도 문제 없이 출력할 수 있는것입니다.

 

bind함수는 함수를 실행하지않고 원본 함수의 복제본을 리턴합니다. 이를 디버깅으로 확인해보았습니다.

첫번째 사진 19줄에서 intorduceMore은 argumets, caller가 null인것으로 봐서 함수가 실행되지 않은것을 판단하였습니다. 

두번째 사진은 19줄을 실행한 이후인데 boundIntroduceMore에 undefined에서 함수가 할당된것을 확인할 수 있습니다. 

 

참고자료

Mozilla 기여자가 작성한 MDN에 대해 CC-BY-SA 2.5 라이선스에 따라 사용할 수 있습니다.

- call, apply, bind

- 화살표함수에서 this

- 명시적 바인딩 용어 및 내용 참고, 암시적 바인딩과 new 바인딩 및 바인딩의 우선순위

댓글