call, apply, bind, Spread Operator

1. call과 apply

함수가 실행될 때 내부 컨텍스트의 this는 실행 중인 객체 자신을 가리키거나 window를 가리킨다. 이때 this가 가리키는 대상을 바꿀 수 있는데, 이렇게 this값을 조작하는데 사용하는 방법이 바로 call()과 apply()이다.

var obj1 = {
  name: 'WEB',
  getName: function() {
    return this.name;
  }
};

var obj2 = {
  name: 'FREE',
  getName: function() {
    return this.name;
  }
};

obj1.getName(); //'WEB'
obj1.getName.call(obj2); //'FREE'

1.1 사용방법

  • call
함수.call(지정할 객체명(컨텍스트), 전달할 매개변수)
  • apply
함수.apply(지정할 객체명(컨텍스트), [전달할 매개변수])

1.2 call()과 apply()의 차이점

동일하게 this 키워드가 가리키는 대상을 변경할 수 있게 해주지만, 인자를 전달하는 방식에 차이가 있다.

  • call: 쉼표로 구분해서 전달
  • apply: 배열 형태로 전달
var myobj = {
  sum: function(a, b) {
    return a + b;
  }
};

myobj.sum.call(null, 1, 2); // 3
myobj.sum.apply(null, [1, 2]); // 3

1.3 확산 연산자(Spread Operator)

만약 this 값이 중요하지 않은 경우 확산 연산자를 그대로 사용할 수 있다. 이 경우 apply와 같은 결과를 얻을 수 있다. 아래 예제에서 this의 값에 null을 쓴 이유는 Math.min과 Math.max가 this와 관계없이 동작하기 때문이다. 즉, 무엇을 넘기든 상관이 없다.

const arr = [2, 3, -5, 15, 7];
Math.min(arr); // NaN
Math.min.apply(null, arr); // -5
Math.min(...arr); // -5
Math.max.apply(null, arr); // 15
Math.max(...arr); //15

2. bind

함수의 this 값을 영구적으로 바꿀 수 있다. call과 apply는 즉시 호출되는 반면, bind는 새로운 함수를 생성한다. 따라서 변수에 담을 수 있다.

var obj1 = {
  name: 'WEB',
  getName: function() {
    return this.name;
  }
};

var obj2 = {
  name: 'FREE',
  getName: function() {
    return this.name;
  }
};

var res = obj1.getName.bind(obj2);
res(); // 'FREE'

Example

const bruce = {
    name: 'Bruce'
  };

  const madeline = {
    name: 'Madeline'
  };

  function greet() {
    console.log(`Hello, I'm ${this.name}`);
  };

  greet(); // undefined
  greet.call(bruce); // Hello, I'm Bruce
  greet.call(madeline); // Hello, I'm Madeline

  function update(birthYear, occupation){
    this.birthYear = birthYear;
    this.occupation = occupation;
  };

  /* call 과 apply 호출
  ***/ 
  update.call(bruce, 1949, 'singer');
  update.call(madeline, 1942, 'actress');

  console.log(bruce); 
  // {name: "Bruce", birthYear: 1949, occupation: "singer"}
  console.log(madeline); 
  // {name: "Madeline", birthYear: 1942, occupation: "actress"}

  update.apply(bruce, [1955, 'actor']);
  update.apply(madeline, [1918, 'writer']);

  console.log(bruce); 
  // {name: "Bruce", birthYear: 1955, occupation: "actor"}
  console.log(madeline); 
  // {name: "Madeline", birthYear: 1918, occupation: "writer"}

  /* Spread operator 를 사용해도 apply와 같은 결과를 얻을 수 있다.
  ***/ 
  newBruce = [1940, "martial artist"];
  update.call(bruce, ...newBruce); 
  // apply(bruce, newBruce)와 같음
  console.log(bruce); 
  // {name: "Bruce", birthYear: 1940, occupation: "martial artist"}

  /* bind를 사용해 this.name의 값을 항상 bruce로 바꿀 수 있다.
  ***/ 
  const updateBruce = update.bind(bruce);
  updateBruce(1904, "actor");
  console.log(bruce); 
  //{ name: "Bruce", birthYear: 1904, occupation: "actor" }

  /* call을 호출해 this.name을 변경해도 Madeline으로 변하지 않는다.
  ***/ 
  updateBruce.call(madeline, 1274, "king");
  console.log(bruce); 
  // { name: "Bruce", birthYear: 1274, occupation: "king" };

  /* bruce가 태어난 해를 1949로 고정하고, 직업은 자유롭게 바꾸는 업데이트 함수
  ***/ 
  const updateBruce1949 = update.bind(bruce, 1949); updateBruce1949("singer, songwriter");
  console.log(bruce); 
  // {name: "Bruce", birthYear: 1949, occupation: "singer, songwriter"}

refs

  • 러닝 자바스크립트