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 객체가 오길 원하지않는다. 어떠한 음식을 불러오는 함수가 오길 원한다. 이것이 바로 this의 Binding이다.
지금부터 원하는 값을 얻기 위해 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$");
다음은 위의 코드에 apply
와 call
이라는 메서드를 추가해 this
값을 obj란 객체
로부터 받아온 코드이다.
결과를 확인해보자.
this
가 더이상 Window 객체를 가리키지 않고 name : "Chicken" 이라는 key, value를 가진 obj
라는 객체를 가리키게 되었다.
자, 이것이 함수의 Binding을 한 예시였다. 그런데 도대체 이 apply
와 call
이 무엇인가에 대해 그리고 어떻게 함수를 Binding 시킨가에 관해 의문이 들 것이다.
지금부터 이 apply
, call
, bind
함수에 관해 알아보자.
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을 이용하여 max
와 min
이라는 새로운 객체를 생성한 예시이다.
가장 위에 언급한 공식문서의 설명을 코드에 적용시켜보면 "주어진 this
값" 이 첫번째 파라미터인 "null
" 이 되는 것이고 "배열로 제공되는 arguments
"가 두 번째 파라미터인 "numbers
" 가 되는 것이다.
이것을 바탕으로 MDN 문서에 나와있는 구문을 보면
func.apply(thisArg , [argsArray]);
를 이해할 수 있게 된다.
push
를 사용하여 요소를 배열에 추가 할 수도 있다. push
는 가변인수를 허용하기 때문에 여러 요소를 동시에 추가 할 수 있다. 그러나 push
를 이용해 배열에 배열을 전달하면 요소를 개별적으로 추가하는 것이 아닌 실제로 해당 배열 자체를 단일 요소로 추가하므로 결국 배열 내부에 배열로 끝이난다.
코드로 살펴보자면 다음과 같다.
const array = ["a", "b"];
const elements = [0, 1, 2];
array.push(elements);
console.log(array);
array라는 배열에 push
메서드를 이용하여 elements라는 배열을 병합시키려고 하는 코드이다. 결과를 확인해보면
다음과 같이 배열안에 배열로 추가는 되었지만 말 그대로 배열 자체 가 단일 요소로 추가 되었다.
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 이라는 새로운 배열을 생성함으로써 가능하게 하였다.
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 라는 배열을 만들지 않고도 바로 배열형태로써 두번째 파라미터에 추가해주면 된다. (중요 !!)
Java와 비슷하게, 객체의 생성자 연결 (chain)에 apply
를 사용할 수 있다. 다음 예에서, Product
객체의 생성자는 name
및 price
를 매개변수로 정의된다. 다른 두 함수 Food
및 Toy
는 this
및 name
과 price
를 전달하는 Product
를 호출한다. Product
는 name
및 price
속성을 초기화하고 , 특수한 두 함수(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);
결과를 확인해보면
다음과 같이 Food
와 Toy
두 함수에 name, price, category의 값을 얻어낼 수 있다.
사실 apply
에 대해 알아보았으면 굳이 call
에 관해 상세히 알아볼 필요는 없다.
apply
와 call
의 역할은 같고, 단지 인자를 넘겨주는 방식의 차이가 있을 뿐이다.
그럼 앞전의 코드들로 call
과 apply
의 차이에 대해 간단히 알아보겠다.
const arrayApply = ["a", "b"];
arrayApply.push.apply(arrayApply, ["c", "d", "e"]);
console.log(arrayApply); // [ "a", "b", "c", "d", "e" ]
==> 위와 같이 각 요소들을 단일 배열로써 두번째 파라미터에 넘겨준다.
const arrayApply = ["a", "b"];
arrayApply.push.call(arrayApply,"c", "d", "e");
console.log(arrayApply); // [ "a", "b", "c", "d", "e" ]
==> apply
와는 다르게 인자를 단일 배열로 받는 것이 아닌 개별 요소 하나하나로 받는다.
이렇게 apply
와 call
은 역할의 차이는 없다고 봐도 무방하다.
bind
도 apply
, call
과 마찬가지의 역할을 한다고 볼 수도 있다. 차이점을 알기 전에 일단 다음코드를 통해 bind
에 대해 알아보자.
const module = {
x: 42,
getX: function () {
return this.x;
},
};
const unboundGetX = module.getX;
console.log(unboundGetX()); //undefined
unboundGetX
에 변수 module
의 getX
함수를 받아와 실행시킨 예시이다. 당연히 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
처음에 만들었던 unboundGetX
에 bind
메서들 이용해 module
객체를 인자로 받았다. 그 후 새로만든 boundGetX
객체에 넣어 호출하면 원하는 값을 얻을 수 있다.
정확히 말하자면 unboundGetX
의 this에 module
을 전달한 것이다.
역할 면에서 따져본다면 큰 차이는 없다. 그냥 구문에 따라 본인이 어떻게 쓰냐에 달렸다고 생각한다. 그래도 구문적인 차이를 살펴보자면 전개하는 방식의 차이가 존재한다.
apply
, call
과 달리 함수를 실행하는 것이 아닌 함수를 생성한 후 ( 위 코드 예시로 봤을 대 boundGetX에 해당) 반환하는 점에서 차이가 있다. ( 새로운 함수를 return )
이번 포스팅을 통해 간단하게 자바스크립트에서 함수의 바인딩과 apply, call, bind 이 메서드가 어떻게 쓰이는지에 대해 알아보았다. 사실 앞으로 이어질 포스팅을 위해 알고 가면 좋을 사전지식 개념에서 작성한 포스팅이다. 이어질 다음내용으로 spread-syntax 및 함수의 this에 관해 작성해보고자 한다.