Javascript Binding과 함께 알아보는 apply(), call(), bind()

DatQueue·2022년 5월 2일
1

Binding이란 무엇일까?

Javascript 에서 함수는 각자 자신만의 this를 가진다. ( this에 관해선 아주 간략하게만 알아보고 다음 포스팅에서 따로 세밀하게 다루겠다. )

설명하기에 앞서 다음 코드를 살펴보자.

const food = function(){
	console.log(this);
    console.log("This is a" + this.name);
}

food();

음식의 이름을 호출하는 아주 간단한 코드이다. 우린 "This is a 음식이름 " 이란 결과를 얻고 싶다. 일단 결과를 먼저 확인해보자.

다음과 같이 this를 호출하였더니 Window 전역객체가 호출이되었고 this.name에는 어떠한 값도 명시되지 않았다.
하지만, 우리는 this의 값으로 Window 객체가 오길 원하지않는다. 어떠한 음식을 불러오는 함수가 오길 원한다. 이것이 바로 thisBinding이다.

지금부터 원하는 값을 얻기 위해 food 함수의 this를 변경해보자. 당연히 this를 대체할 객체가 필요하다.

const obj = {name : "Chicken"}

const food = function(price){
   console.log(this);
   console.log(`This is a ${this.name} , ${this.name} is ${price}`);
}

food.apply(obj,["10$"]);
food.call(obj,"10$");

다음은 위의 코드에 applycall 이라는 메서드를 추가해 this값을 obj란 객체로부터 받아온 코드이다.

결과를 확인해보자.

this가 더이상 Window 객체를 가리키지 않고 name : "Chicken" 이라는 key, value를 가진 obj라는 객체를 가리키게 되었다.

자, 이것이 함수의 Binding을 한 예시였다. 그런데 도대체 이 applycall이 무엇인가에 대해 그리고 어떻게 함수를 Binding 시킨가에 관해 의문이 들 것이다.

지금부터 이 apply , call , bind 함수에 관해 알아보자.

⪧ apply( )

apply에 관한 MDN 공식문서를 확인해보면 다음과 같다.

apply( ) 메서드는 주어진 this 값과 배열 ( 또는 유사 배열 객체 ) 로 제공되는 arguments로 함수를 호출한다.

글이 더 이해가 안가므로 예제를 통해서 확인해보자.

const numbers = [5,6,7,8,9];
const max = Math.max.apply(null, numbers);
const min = Math.min.apply(null, numbers);
console.log(max); // 9
console.log(min); // 5

numbers라는 array를 apply의 두번째 parameter로 받았고 첫번째 파라미터로는 null을 받았다. 그리고 Math.max와 min을 이용하여 maxmin이라는 새로운 객체를 생성한 예시이다.

가장 위에 언급한 공식문서의 설명을 코드에 적용시켜보면 "주어진 this 값" 이 첫번째 파라미터인 "null" 이 되는 것이고 "배열로 제공되는 arguments"가 두 번째 파라미터인 "numbers" 가 되는 것이다.

이것을 바탕으로 MDN 문서에 나와있는 구문을 보면

func.apply(thisArg , [argsArray]);

를 이해할 수 있게 된다.

배열에 배열을 붙이기 위해 apply 사용

1. push 사용

push를 사용하여 요소를 배열에 추가 할 수도 있다. push는 가변인수를 허용하기 때문에 여러 요소를 동시에 추가 할 수 있다. 그러나 push를 이용해 배열에 배열을 전달하면 요소를 개별적으로 추가하는 것이 아닌 실제로 해당 배열 자체를 단일 요소로 추가하므로 결국 배열 내부에 배열로 끝이난다.

코드로 살펴보자면 다음과 같다.

const array = ["a", "b"];
const elements = [0, 1, 2];
array.push(elements);
console.log(array);

array라는 배열에 push 메서드를 이용하여 elements라는 배열을 병합시키려고 하는 코드이다. 결과를 확인해보면

다음과 같이 배열안에 배열로 추가는 되었지만 말 그대로 배열 자체 가 단일 요소로 추가 되었다.

2. concat 사용

concat 이라는 메서드를 사용하면 단일 요소로써 배열에 다른 배열의 요소들을 추가할 수 있다. 그렇지만 이 방법도 썩 원하는 결과를 불러오지 못할지도 모른다. 다음 코드를 통해 알아보자.

const num1 = [1, 2, 3];
const num2 = [4, 5, 6];
const num3 = [7, 8, 9];

const addNum = num1.concat(num2, num3);
console.log(addNum);

num1에 num2, num3 배열을 concat메서드를 이용해 추가한다음 addNum이라는 새로운 배열로 받은 형태이다.

그렇다, 단일 요소로써 배열을 합칠 수는 있지만 num1 배열 자체가 병합된 배열이 되는 것이 아닌 addNum 이라는 새로운 배열을 생성함으로써 가능하게 하였다.

3. apply 사용

apply 를 사용하면 위의 push , concat에서 겪은 문제를 해결할 수 있다.

const array = ["a", "b"];
const elements = [0, 1, 2];
array.push.apply(array, elements);
console.log(array);

// or

const array = ["a", "b"];
array.push.apply(array, [0, 1, 2]);
console.log(array);

결과를 확인해보면

새로운 배열이 아닌 기존 array라는 배열에 elements배열이 단일 요소로써 추가된 것을 확인할 수 있다. 간단한 코드를 작성하고자 할때는 elements 라는 배열을 만들지 않고도 바로 배열형태로써 두번째 파라미터에 추가해주면 된다. (중요 !!)

객체의 생성자 연결에 apply 사용

Java와 비슷하게, 객체의 생성자 연결 (chain)에 apply를 사용할 수 있다. 다음 예에서, Product 객체의 생성자는 nameprice 를 매개변수로 정의된다. 다른 두 함수 FoodToythisnameprice 를 전달하는 Product를 호출한다. Productnameprice 속성을 초기화하고 , 특수한 두 함수(Food 및 Toy)는 category를 정의한다.

그럼 예시 코드를 살펴보자.

function Product(name, price) {
  this.name = name;
  this.price = price;

  if (price < 0) {
    throw new Error(
      "Cannot create product" + this.name + "with a negative price"
    );
  }
}

function Food(name, price) {
  Product.apply(this, [name, price]);
  this.category = "food";
}

function Toy(name, price) {
  Product.apply(this, [name, price]);
  this.category = "toy";
}

const meal = new Food("pizza", 5);
const fun = new Toy("robot", 40);

console.log(meal);
console.log(fun);

결과를 확인해보면

다음과 같이 FoodToy 두 함수에 name, price, category의 값을 얻어낼 수 있다.

⪧ call( )

사실 apply에 대해 알아보았으면 굳이 call에 관해 상세히 알아볼 필요는 없다.
applycall의 역할은 같고, 단지 인자를 넘겨주는 방식의 차이가 있을 뿐이다.

그럼 앞전의 코드들로 callapply의 차이에 대해 간단히 알아보겠다.

1. apply로 배열 합치기

const arrayApply = ["a", "b"];
arrayApply.push.apply(arrayApply, ["c", "d", "e"]);
console.log(arrayApply); // [ "a", "b", "c", "d", "e" ]

==> 위와 같이 각 요소들을 단일 배열로써 두번째 파라미터에 넘겨준다.

2. call로 배열 합치기

const arrayApply = ["a", "b"];
arrayApply.push.call(arrayApply,"c", "d", "e");
console.log(arrayApply); // [ "a", "b", "c", "d", "e" ]

==> apply와는 다르게 인자를 단일 배열로 받는 것이 아닌 개별 요소 하나하나로 받는다.

이렇게 applycall은 역할의 차이는 없다고 봐도 무방하다.

⪧ bind( )

bindapply, call 과 마찬가지의 역할을 한다고 볼 수도 있다. 차이점을 알기 전에 일단 다음코드를 통해 bind에 대해 알아보자.

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

const unboundGetX = module.getX;
console.log(unboundGetX());  //undefined

unboundGetX에 변수 modulegetX함수를 받아와 실행시킨 예시이다. 당연히 getX의 return값인 x : 42. 즉, 42가 출력될 것으로 예상했지만 예상과 달리 undefined가 실행되었다.

즉, unboundGetX함수의 this에는 name이라는 프로퍼티가 존재하지 않았던 것이고 자연스래 window객체를 참조하게 되고 window에 존재하지 않으므로 undefined가 호출된 것이다.

자, 이제 bind를 이용하여 이 문제를 해결해보자.

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

const unboundGetX = module.getX;
console.log(unboundGetX());

const boundGetX = unboundGetX.bind(module);   // bind 사용
console.log(boundGetX()); //42

처음에 만들었던 unboundGetXbind 메서들 이용해 module객체를 인자로 받았다. 그 후 새로만든 boundGetX 객체에 넣어 호출하면 원하는 값을 얻을 수 있다.
정확히 말하자면 unboundGetXthismodule전달한 것이다.

apply, call 과 bind의 차이점?

역할 면에서 따져본다면 큰 차이는 없다. 그냥 구문에 따라 본인이 어떻게 쓰냐에 달렸다고 생각한다. 그래도 구문적인 차이를 살펴보자면 전개하는 방식의 차이가 존재한다.
apply, call과 달리 함수를 실행하는 것이 아닌 함수를 생성한 후 ( 위 코드 예시로 봤을 대 boundGetX에 해당) 반환하는 점에서 차이가 있다. ( 새로운 함수를 return )

마무리 ...

이번 포스팅을 통해 간단하게 자바스크립트에서 함수의 바인딩과 apply, call, bind 이 메서드가 어떻게 쓰이는지에 대해 알아보았다. 사실 앞으로 이어질 포스팅을 위해 알고 가면 좋을 사전지식 개념에서 작성한 포스팅이다. 이어질 다음내용으로 spread-syntax 및 함수의 this에 관해 작성해보고자 한다.

profile
You better cool it off before you burn it out / 티스토리(Kotlin, Android): https://nemoo-dev.tistory.com

0개의 댓글