[Javascript] call, bind, apply

call
자바스크립트에서 함수는 선언한 후 일단 호출을 해야 실행된다. 함수를 호출하는 방법으로는 함수 뒤에 ()를 붙이는 것과 call 또는 apply를 통해 호출하는 방법이 있다.
var example = function (a, b, c) {
return a + b + c;
};
example(1, 2, 3);
example.call(null, 1, 2, 3);
example.apply(null, [1, 2, 3]);
위 예시를 보면 call()은 보통 함수와 똑같이 인자를 넣고, apply는 인자를 하나로 묶어 배열로 만들어 넣는 것을 알 수 있다. 그렇다면 여기서 call과 apply가 공통적으로 가진 첫 번째 인자 null의 역할은 뭘까?
바로 this를 대체하는 것이다.
var obj = {
string: 'zero',
yell: function() {
alert(this.string);
}
};
var obj2 = {
string: 'what'
};
obj.yell(); // 'zero';
obj.yell.call(obj2); // 'what'
// obj1.yell()을 obj2.yell()로 바꾼 효과라고 보면 된다.
위 예시 마지막 라인에서는 obj.yell.call(obj2)를 통해 this가 가리키는 것을 obj에서 obj2로 바꿨다. 따라서 yell은 obj의 메서드임에도 불구하고 'zero' 대신 'what'이 alert의 메시지로 표출될 수 있게 되는 것이다.
var kim = { name:"kim", first:10, second:20 }
var lee = { name:"lee", first:100, second:200 }
function sum(num) {
return num + this.first + this.second;
}
sum.call(kim, 500); // sum()을 call하는데 this값을 kim 객체로 바꾼다
자바스크립트에서는 함수도 객체기 때문에 원래 같으면 this가 전역 window를 가리킬 것을 위와 같은 방식으로도 지정이 가능하다.
즉 call을 써서 this를 정의해 준다면 다른 객체의 파라미터나 메서드를 자기 것마냥 사용할 수 있는 것이다.
.call(this, 파라미터1, 파라미터2 ...)
.apply(this, [ ]);
this는 기본적으로 전역 객체 window로 정해져 있다. 하지만 몇 가지 방법으로 window를 다른 것으로 바꿀 수 있는데 call, apply, bind에서 첫 번째 인자로 다른 것을 넣어주는 것이 this를 바꾸는 방법 중 하나이다.
위 메서드들을 쓰는 예로 함수의 arguments를 조작할 때가 있다.
arguments란 함수라면 처음부터 갖고 있는 숨겨진 속성으로 함수에 들어온 인자들을 배열 형식으로 반환하는 것이다(정확히는 배열이 아니라 유사배열).
function example() {
console.log(arguments);
}
example(1, 'string', true); // [1, 'string', true]
생긴 건 배열이지만 배열이 아니라 유사배열이기 때문에 그 자체로 배열의 메서드는 사용할 수 없다. 따라서 배열의 내장함수 등을 사용하려 하면 아래와 같이 에러가 발생한다. arguments는 생긴 모양만 배열이지 실제 배열이 아니기 때문이다.
function example2() {
console.log(arguments.join()); // Array.prototype.join() 메소드는 사용할 수 없다.
}
example2(1, 'string', true); // Uncaught TypeError: arguments.join is not a function
그리고 바로 이 때 call이나 apply가 효력을 발휘한다.
function example3() {
console.log(Array.prototype.join.call(arguments));
}
example3(1, 'string', true); // '1,string,true'
배열의 프로토타입에 있는 join 함수를 빌려 쓰는 것이다. this는 arguments를 가리키도록 하고 말이다.
위 내용을 정리해 보자면 이렇다.
1. join()을 통해 배열을 문자열로 반환하려고 한다.
2. 함수 인자로 값들을 넘긴다. arguments를 통해 배열 형태로 받으려 하지만 이는 유사 배열이므로 arguments.join()은 에러를 발생시킨다.
3. 우선은 프로토타입 메서드 Array.prototype.join()을 불러오고 그 뒤에 .call()로 join 함수를 조작한다.
4. call(arguments)로 this를 유사 배열을 가리키게 한다. 본래는 Array 객체를 가리키고 있지만 이를 바꾼 것이다.
5. 최종적으로 Array.prototype.join()이 [1, 'string', true].join()이 된다.
join() 외의 모든 메서드들을 이와 같은 방식으로 사용할 수 있다.
요약해 보자면 아래와 같다.
const items = [1, 4];
items.join() // "1,4"
Array.prototype.join.call(items); // "1,4"
[].join.call(items); // "1,4"
apply
apply()의 대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용하는 경우이다.
arguments 객체는 배열이 아니기 때문에 본래 Array 객체의 slice() 같은 배열의 메서드를 사용할 수 없으나, apply()를 사용하면 이것이 가능해진다.
function convertArgsToArray() {
console.log(arguments);
// arguments 유사배열 겍체를 this로 사용
// slice: 배열의 특정 부분에 대한 복사본을 생성함.
var arr = Array.prototype.slice.apply(arguments); // arguments.slice
// var arr = [].slice.apply(arguments);
console.log(arr);
return arr;
}
convertArgsToArray(1, 2, 3); // [1,2,3]
Array.prototype.slice.apply(arguments)는, Array.prototype.slice()를 호출하되 this는 arguments 객체로 바인딩하라는 의미가 된다.
결국 Array.prototype.slice()를 arguments 객체 자신의 메서드인 것처럼 arguments.slice()와 같은 형태로 호출하라는 의미이다.

bind
bind 함수는 함수가 가리키는 this만 바꾸고 호출하지는 않는 것이다. 정확히 말하면 this를 정의하고 나서 그 함수를 복사해 새로운 함수를 만들어 리턴하는 것이다.
var obj = {
string: 'zero',
yell: function() {
alert(this.string);
}
};
var obj2 = {
string: 'what'
};
var yell2 = obj.yell.bind(obj2);
yell2(); // 'what'
obj.yell.bind(obj2)(); // 'what'
obj.yell.bind(obj2)를 실행하니 yell 함수의 this가 obj2로 바뀌었다. 즉 call이나 apply와 비슷하지만 호출은 하지 않고 함수만 반환하는 것이다.
cf. call(this, 1, 2, 3)은 bind(this)(1, 2, 3)과 같다
정리
call, apply, bind의 역할은 참조하는 함수에 대한 조작이다. 뭘 조작하냐면 this를 조작하여 해당 함수가 마치 어느 객체 안에 있는 것처럼 행동하게 하는 것.
var obj = { ..., yell(){} };
var obj2 = { ... };
obj.yell.call(obj2); == obj2.yell();
var arr = [ ... ];
Array.prototype.join.call(arr) == [].join.call(arr) == arr.join();
call, apply는 적용 후 바로 호출하며 bind는 호출은 안 하고 반환만 한다는 차이가 있다. 단, bind()()와 같이 사용한다면 call이나 apply처럼 사용 가능하다.
apply는 인수를 배열로 받는 call이다. apply는 여러 개의 인수를 적용하고 싶은데 상수가 아닌 변수로 적용하려 할 때 주로 쓰인다. 그 예로 아래와 같이 (1, 2, 100, 200) 같은 인자들을 넘기려 하는데 상수가 아닌 변수나 묶음으로 관리하여 주고 싶을 때 [1, 2, 100, 200]을 넘기고 apply 함수를 사용하면 call(arr, 1, 2, 100, 200)와 같이 사용하는 효과를 얻을 수 있다.
var arr = [1,2,3,4];
var inputArgument = [1,2,100,200];
[].splice.apply(arr, inputArgument); // [1, 100, 200, 4]
// == [].splice.call(arr, 1,2,100,200);