React Native #1 React 후려치기
Javascript #8 자바스크립트의 call, apply, bind에 대해 알아봅니다.
2019-03-09
Explanation
이번 글은 자바스크립트의 call, apply, bind 메서드에 대해 알아봅니다. 자바스크립트에서는 함수도 객체인데요. 뜬금 이 이야기를 하는 이유는, call, apply, bind가 함수 객체의 메서드이기 때문이에요.
함수를 호출하는 방법으로는 생각해보면 아래와 같은 방법이 있는데요.
1 2 3 4 5 6 7 |
function fnc() { return 'hi'; } fnc(); fnc.call(); fnc.apply(); |
그런데, 첫번째 방법처럼 ( ) 만으로도 간단하게 호출이 가능한데 굳이 call과 apply 메서드가 있다는건 뭔가 다른 쓰임이나 이유가 있겠죠?
우선 fnc( ) 처럼 함수를 호출할때 저 ( ) 안의 파리미터값으로 함수에 사용되는 인자를 함께 전달 할 수 있어요.
1 2 3 4 5 6 |
function fnc(name) { console.log(`hi ${name}`); } fnc('falsy'); // hi falsy |
그리고 call과 apply는 조금 다른 파라미터 값을 가지고 있어요.
우선 call 메서드에 대해서 MDN을 참고하면,
1 |
fun.call(thisArg[, arg1[, arg2[, ...]]]) |
여기에서 ‘thisArg’는 ‘fun’이라는 함수에서 사용되는 this를 정의해주는 값이고, 이후 파라미터값들을 ‘,’ 로 추가할 수 있어요, 예를 들면
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function fnc(prop) { console.log(this[prop]); } const callObj = { hello: 'world1' }; function callFnc() { this.hello = 'world2'; } fnc.call(callObj, 'hello'); // world1 fnc.call(new callFnc(), 'hello'); // world2 |
굉장히.. 이상한 예제이지만 대략 이렇게 사용한다면 fnc( ) 함수의 재사용성을 굉장히 높일 수 있겠죠??
그리고 ES6 이전의 프로토타입 체인을 이용한 상속에서도 call을 사용할 수 있습니다.
우선 ES6에 추가된 class를 사용한 코드를 보면 아래와 같은데요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class A { constructor(a) { this.parentA = a * 2; } } class B extends A { constructor(a, b) { super(a); this.childrenB = b * 3; } } const b = new B(2, 2); console.log(b); // { parentA: 4, childrenB: 6 } |
위 코드를 ES6 이전의 코드로 구현하면 아래와 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
function A(a) { this.parentA = a * 2; } function B(a, b) { A.call(this, a); this.childrenB = b * 3; } B.prototype = Object.create(A.prototype); B.prototype.constructor = B; const b = new B(2, 2); console.log(b); // { parentA: 4, childrenB: 6 } |
조만간.. 자바스크립트의 프로토타입에 대해서도 글을 적어보려고 하는데요..
우선은 이렇게 ES6의 super()의 역할을 할 수 있도록 하는데에 사용할 수도 있답니다.
글을 적고 보니 좀 이상하네요.. call이 먼저 있던 메소드이고 super가 후에 나온 메서드이니.. 정확하게는 super가 class에서 부모의 constructor를 수행하기 위해 사용하는 call(또는 apply)를 대신하여 새로 만들어진 메서드라는 맞는 표현이겠네요. 여하튼, 대충.. call 메서드는 이러하답니다.
다음으로 apply 메서드 인데요. apply 메서드는 call 과 거의 비슷한데 지원하는 인수의 타입이 조금 달라요. call이 파라미터로 리스트(a, b, c..)를 사용하지만 apply는 파라미터의 배열로 사용할 수 있습니다. 예로 MDN에서 소개된 예를 보자면,
1 2 3 4 |
var array = ['a', 'b']; var elements = [0, 1, 2]; array.push.apply(array, elements); console.info(array); // ["a", "b", 0, 1, 2] |
보통 두개의 배열을 합치기 위해 Array.concat()을 많이 사용하지만 위와 같이 구현하면 새로운 배열을 생성하지 않고 array라는 배열에 elements라는 배열을 합칠 수 있답니다.
부가적으로 ES6 이후로는 apply의 파라미터로 NodeList 또는 { ‘length’: 2, ‘0’: ‘eat’, ‘1’: ‘bananas’ } 와 같은 커스텀 객체를 사용할 수 있다고 하네요~
그리고 대부분 call 메서드와 비슷하게 사용된답니다.
우선 bind 메서드는 call과 비슷한데요, (이렇게 말하니,, call, apply, bind가 다 비슷하네요.. 여하튼..) 우선 MDN을 참고해서 bind의 구문을 살펴보면요
1 |
fun.bind(thisArg[, arg1[, arg2[, ...]]]) |
call의 구문과 똑같죠?
bind 메서드가 call, apply와 다른점은 call과 apply는 바로 함수를 호출하고 bind는 호출하지 않고 바인딩된 새로운 함수를 만듭니다. 이렇게 글로만 적으면 헷갈리니 예를 들어보면요
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function fnc(prop) { return this[prop]; } const callObj = { hello: 'world' }; const callValue = fnc.call(callObj, 'hello'); console.log(callValue); // 'world'; const bindValue = fnc.bind(callObj, 'hello'); console.dir(bindValue); //fnc(prop) { // return this[prop]; //} console.log(bindValue()); // 'world' |
이렇게 bind 메서드를 사용하면 callObj를 this로 가지고 있는 fnc 함수를 감싸는 바운드 함수가 만들어 진답니다. 여기서 만들어진 바운드 함수는 흔히 사용하는 함수와는 다른 함수로. 내부적으로 감싸고 있는 함수(위 예에선 fnc), 함수에 전달하도록 한 this값(위 예에선 callObj), *[BoundArguments : 어떤 감싸인 함수 호출에든 첫 번째 인수로 사용되는 요소를 갖는 값 목록], 그리고 호출의 속성을 가지고 있습니다.
위에서 * 별표한 BoundArguments 부분이 정확히 무엇을 말하는 건지 조금 헷갈리는데요. 아마도 아래의 예제에서 37이 사용되는 걸 말하는 거 같아요. 바인드된 새로운 함수를 만들때 사용되는 초기값 같은 인자??
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function list() { return Array.prototype.slice.call(arguments); } var list1 = list(1, 2, 3); // [1, 2, 3] // 미리 설정된 선행 인수가 있는 함수 생성 var leadingThirtysevenList = list.bind(undefined, 37); var list2 = leadingThirtysevenList(); // [37] var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3] |
끝으로 bind 메서드는 ECMAScript 5.1에 추가되어서 IE8 에서는 사용할 수 없는데요, MDN에 소개된 폴리필이 bind를 이해하는데 조금 더 도움이 될것 같아서 추가합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // ECMAScript 5 내부 IsCallable 함수와 // 가능한 가장 가까운 것 throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; if (this.prototype) { // Function.prototype은 prototype 속성이 없음 fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound; }; } |
참고링크.
1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/call
2. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
3. https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
끝