[TIL] JS_함수 메소드(apply, call, bind) 정리

jomminii_before·2019년 10월 28일
10

TIL

목록 보기
3/3
post-thumbnail

코드스테이츠 함수 메소드 강의 내용 정리

메소드 기본 개념

apply, call, bind 각 메소드는 아래와 같이 사용할 수 있습니다.

[apply]
작성법 : fn.apply(thisArg, [argsArray])
this 인자를 첫번째 인자로 받고, 두번째 인자로는 배열을 받음
[call]
작성법 : fn.call(thisArg[, arg1[, arg2[, ...]]])
this 인자를 첫번째 인자로 받고, 두번째 인자부터는 배열이 아닌 각 인자로 받음
[bind]
작성법 : fn.bind(thisArg[, arg1[, arg2[, ...]]])
call과 인자 작성법은 같으나, apply, call과 달리 바로 메소드가 실행되지 않음. thisArg를 바인딩하는 역할만 함.

사용 예시

function add(x, y) {
  this.val = x + y;
  console.log(this.val);
}

let obj = { val : 0 };

add.apply(obj, [2, 8]); // 10 
add.call(obj, 2, 8); // 10 

let boundFn = add.bind(obj, 2, 8); // 이때는 obj.val 값이 바뀌어있지 않음. 

boundFn(); // 10, add는 여기서 실행됨. 일반적인 function (method) 호출로 보이지만, 여기엔 this 값이 이미 바인딩 되어있음

1. apply 메소드 활용

Math.max() 메소드를 사용하면, arguments로 받은 숫자 값 중에서 가장 큰 값을 뽑아줍니다.
하지만, 해당 arguments가 배열로 이루어져 있을 경우에는 바로 값을 뽑을 수 없습니다.(NaN 반환) Math.max()의 argument에는 숫자만 들어갈 수 있기 때문인데요, 이때 .apply를 쓰면 이를 해결할 수 있습니다.

Math.max(12,4,5,1,2,3,6,10) // 12

let arr = [12,4,5,1,2,3,6,10];
Math.max(arr) ; // NaN
Math.max.apply(null,arr) // 12

.apply에서는 두 번째 인자로 배열을 받고, 그 배열을 각각의 인자로 배정해서 계산을 가능하게 해줍니다. Math.max 메소드에서는 this 역할이 없기 때문에 첫번째 인자에 들어가는 값은 중요하지 않습니다.

2. call, apply 메소드 활용

call 또는 apply를 이용해 주체가 되는 인스턴스와 메소드의 순서를 바꿀 수 있습니다.

기존의 filter 메소드 사용법array.filter(function())을 보면, filter 메소드는 함수를 argument(인자)로 받고, 이를 앞에 있는 인스턴스인 array에 적용합니다. 이부분을 call과 apply의 arguments에 대입해보면, 첫번째 argument인 thisArg 자리에는 array가, 두번째 argument인 arg 자리에는 함수가 들어갑니다.

function moreThanFive(element) {
	return element.length > 5;
}

let arr = ['abc', 'defghi'];
arr.filter(moreThanFive); // ['defghi'] => 인스턴스(arr)가 먼저 나오고, 그 뒤에 메소드(filter)가 등장

Array.prototype.filter.call(arr, moreThanFive); // ['defghi'] => 메소드(filter)가 먼저 나오고, 인스턴스(arr)이 뒤에 나옴
Array.prototype.filter.apply(arr,[moreThanFive]); // ['defghi'] // 위 call과 동일

저 같은 경우는 개념이 잘 정리되지 않은 상태에서 처음 이 사용법을 만나니 이해가 잘 안갔는데, filter 메소드의 기존 사용법에 각 argument 들을 대입해보니 이해가 되기 시작했습니다.

여기까지만 보면 굳이 인스턴스와 메소드의 순서를 바꿀 이유가 없지만, 이 방법을 사용하면 배열 메소드를 유사 배열(array-like object)에 적용시킬 수 있습니다.

자바스크립트에서 DOM 구조를 이용해 element들을 불러오면, 유사배열이 생기는데요, 아래 이미지는 네이버에서 긁어온 리스트 요소들을 보여주고 있습니다.

naver_li.png

이미지를 보면 알 수 있듯이 긁어온 리스트 요소는 배열이 아닌 "NodeList"로, 유사배열의 형태를 보이고 있습니다. 유사배열의 경우, 기존의 filter, map과 같은 배열 메소드를 사용하지 못하는데요, 이때, call, apply 메소드로 prototype 기능을 빌려쓰면 배열 메소드를 사용할 수 있게 됩니다.

const list = document.querySelectorAll("li");

function getElementId(element) { // element의 아이디를 가져오는 함수
  return element.id;
}

Array.prototype.map.call(list, getElementId); //유사배열의 요소들을 배열의 요소처럼 사용할 수 있게 함

이렇게 prototype 기능을 빌려쓰면 아래처럼 유사배열(NodeList)을 배열처럼 사용해 값을 뽑을 수 있습니다.
naver_lis.png

3. bind 메소드 활용 (특정 함수가 this 값을 바꿔버리는 경우)

function Box(w,h) {
  this.width = w;
  this.height = h;
  
  this.getArea = function() {
    return this.width * this.height;
  }
  
  this.printArea = function() {
    console.log(this.getArea());
  }
}

let b = new Box(100, 50) ;
b.printArea(); // 5000

setTimeout(b.printArea, 2000); // 2초 후 b.printArea를 실행

Box라는 생성자 함수를 사용해 b라는 인스턴스를 만들었습니다. 여기서 this는 "b"가 되므로, b.printArea() 값은 5000이 나옵니다. 그런데 setTimeout()에 이 메소드를 넣으면 아래와 같은 에러가 발생합니다.

20191028_125118.png

위에서는 "this"가 "b"로 잡혔지만, 이번에는 "this"가 "window"로 잡혀버렸기 때문인데요, 이건 setTimeout() 메소드 자체의 특성 때문에 그렇습니다.

thisproblem.png

mdn에서는 setTimeout() 메소드의 경우, 별도로 this 키워드를 설정하지 않으면, this를 "window"로 받는다고 설명하고 있습니다. 이 상황을 처리하기 위해 bind를 사용할 수 있습니다.

setTimeout(b.printArea.bind(b), 2000); // 2초 후 5000 콘솔에 찍힘

위와 같이 argument를 작성하면, b.printAreathis를 인스턴스 b로 지정할 수 있습니다. 하나 참고해야할 건 setTimeout()에 들어가는 첫번째 argument는 함수 실행이 아니라 함수라는 점인데요, 마지막에 (b)가 들어가서 함수가 실행된 것처럼 보이나, 이는 bind를 하는 방식이 그런 것이므로 함수를 실행한 건 아닙니다.

4. bind 메소드 활용 (커링 함수, Curring function)

커링함수는 "argument(인자) 여러개를 받는 함수"를 argument 몇 개를 미리 채움으로써 더 간단히 만든 함수 입니다. 커링함수는 재사용성을 높여준다는 측면에서 많이 사용되는데요, 예시는 아래와 같은 것이 있습니다.

// argument 2개를 받아서 함수 실행
let sumA = function (x, y){
  return x + y;
};
sumA(8,4); // 12

let sumB = function (x){
  return function (y) {
    return x + y ;
	}
}
// 하나의 argument를 저장해놓고, 다른 나머지 하나의 argument만 받아서 함수 실행
let sum8 = sumB(8) ; 
console.log(sum8(4)); //12

이러한 커링함수를 bind로도 만들어서 사용할 수 있습니다.

function template(name, money) {
  return '<h1>' + name + '</h1><span>' + money + '</span>';
}

// this가 활용되지 않기 때문에 첫번째 argument는 null로 설정
// 미리 바인드 시켜놓을 argument를 두번째 argument에 작성
let tmplSteve = template.bind(null, 'Steve'); 

//template의 첫번째 인자에 Steve가 들어간 채로 바인딩 된 tmplSteve를 활용
tmplSteve(100) ; // <h1>Steve</h1><span>100</span>

let tmplJohnny = template.bind(null, 'Johnny') ; 
tmplJohnny(500) ; // <h1>Johnny</h1><span>500</span>

위와 같이 미리 argument를 바인딩 시켜놓으면, 커링함수처럼 계속 불러와 함수를 재사용할 수 있습니다.

지금까지 정리를 해봤는데요, 이렇게 정리를 하면서 조금은 메소드 개념과 가까워진 것 같지만, 실제로 활용해봐야 제대로 습득이 될 것 같네요...!

다음 공부하러 총총...

profile
https://velog.io/@jomminii 로 이동했습니다.

1개의 댓글

comment-user-thumbnail
2020년 5월 7일

누가 이런 유용한 정리를 했나 했더니 풀스택 devmin 아저씨였네요...

답글 달기