본문 바로가기
Javascript

[Javascript] Callbacks: forEach, map, filter

by kmmguumnn 2018. 4. 14.

Callback Functions


지난 포스트에서 언급했듯이, Javascript의 함수는 first-class 함수다. first-class 함수의 특성 중 하나는 일반적인 값들을 함수에 넘겨주듯, 함수를 함수에 넘겨주는 것 역시 가능하다는 것이었다. 다른 함수를 인자로 받아들이거나 return하는 함수를 "higher-order function"이라 칭하고, 다른 함수에 인자로 들어가는 함수를 우리는 "Callback function"이라고 부른다. 


특히 배열에서 콜백 함수를 자주 볼 수 있는데, 배열 메소드의 내부로 함수가 전달되는 것이다. 이번 글에서는 배열에서 콜백 함수와 함께 자주 활용되는 다음의 3가지 메소드에 대해 알아 보도록 한다.


forEach()

map()

filter()




forEach()


배열에서 forEach() 메소드는 콜백 함수를 인자로 받아서, 배열의 원소 각각에 대해 콜백함수를 수행한다. 즉 forEach()는 배열 전체를 돌면서 요소 전체에 대해 반복적인 작업을 수행하도록 해 준다. 다음의 예시를 보자.

array.forEach(function callback(currentValue, index, array) {
    // function code here
});

forEach 내부의 callback 함수는 3개의 인자를 받는다. array의 요소 각각에 대해 callback 함수의 body 부분을 수행하게 된다.



다음으로는 숫자 하나를 받아 그 숫자가 홀수이면 콘솔에 숫자를 찍는 간단한 함수 logIfOdd()가 있다고 하자.

function logIfOdd(n) {
  if (n % 2 !== 0) {
    console.log(n);
  }
}

logIfOdd(2);
// (nothing is logged)

logIfOdd(3);
// 3

이 함수에 2를 전달하면, 2는 짝수이므로 콘솔에 아무것도 찍히지 않고, 3을 전달하면 홀수이므로 콘솔에 찍히게 된다. 하지만 logIfOdd() 함수는 개별적인 숫자 하나만 받을 뿐, 배열 전체를 처리할 수는 없다. 이제 콜백을 활용해 보자.


[1, 5, 2, 4, 6, 3].forEach(function logIfOdd(n) {
  if (n % 2 !== 0) {
    console.log(n);
  }
});

// 1
// 5
// 3

배열 [1, 5, 2, 4, 6, 3] 중 홀수만 콘솔에 출력된다. 주목할 것은 배열의 forEach 메소드에 logIfOdd() 함수가 인자로 전달된 부분이다. forEach()가 배열의 요소들 각각에 대해 logIfOdd()를 호출하는 것이다. 



혹은 배열에서 forEach()의 인자로 익명 함수를 전달하는 것 역시 꽤 흔한 패턴이다.

[1, 5, 2, 4, 6, 3].forEach(function (n) {
  if (n % 2 !== 0) {
    console.log(n);
  }
});

// 1
// 5
// 3


혹은 따로 정의된 함수를 이름만으로 불러오는 방법도 있다.

[1, 5, 2, 4, 6, 3].forEach(logIfOdd);

// 1
// 5
// 3



map()


map() 메소드는 배열 요소들 각각에 대해 콜백 함수를 호출한다는 점에서 forEach()와 유사한데, 차이점은 map()은 콜백 함수로부터 return 받은 것을 기반으로 새로운 배열을 만들어 return한다는 점이다. 다음의 코드를 보자.

const names = ['David', 'Richard', 'Veronika'];

const nameLengths = names.map(function(name) {
  return name.length;
});

map() 메소드에게 전달된 콜백 함수는 names 배열 요소들 각각에 대해 호출된다. 배열의 첫번째 요소를 받아 'name' 변수에 저장한 뒤 그것의 길이를 return하는 구조다. 나머지 요소들에 대해서도 똑같이 진행된다.

forEach()는 아무것도 return하지 않고, map()은 함수로부터 return 받은 값으로 새로운 배열을 return한다는 점을 기억하자. 따라서 nameLengths는 새로운 배열인 것이고([5, 7, 8]), 기존의 names 배열은 변하지 않고 그대로 남아있다. 


Array.prototype.map() MDN 문서 참조




filter()


filter() 메소드는 map() 메소드와 유사한데, 이유는

  • 배열에서 호출되고
  • 함수를 인자로 받고
  • 새로운 배열을 return하기 때문이다.

차이점은 filter()에 전달된 함수는 테스트 용도로 사용되고, 테스트를 통과한 배열의 항목만 새 배열에 포함된다는 것이다. 다음의 코드를 보자.

const names = ['David', 'Richard', 'Veronika'];

const shortNames = names.filter(function(name) {
  return name.length < 6;
});

역시 마찬가지로 filter() 메소드도 인자로 전달받는 함수가 'names' 배열의 요소들 각각에 대해 호출된다. names 배열의 첫번째 요소가 name 변수에 저장되고, 그리고 나서 테스트가 수행되는데, 일종의 필터링 작업이다(메소드 이름이 filter()인 것을 떠올리자). 

name의 길이를 체크한다. 오로지 6보다 작은 경우에만 true를 return하고 새로운 배열에 포함될 수 있다. 필터링 작업을 포함한 모든 과정이 끝나면, 새로운 배열이 filter() 메소드로부터 return된다. 이 역시 원래 배열인 names에는 아무 변화를 일으키지 않는다.

const shortNames = names.filter(function(name) {
  return name.length < 6;
});

console.log(shortNames);
// ['David']

Array.prototype.filter() MDN 문서 참조





더 많은 Array 메소드는 Array.prototype (MDN)

댓글