비슷하지만 다른 세 함수들, 언제 써야 할까?
보통 배열 내 인자들을 차례로 순회할 때 for문을 많이 사용합니다. 단순하지만 확실한 방법이죠. 하지만 조금만 JavaScript를 살펴보면 for문보다 더 유용하고 간결하게 코드를 작성하게 해주는 메서드를 만날 수 있습니다.
그래서 오늘은 배열에 사용하는 메서드 중에서도 비슷한 기능을 하는 세 함수. reduce, map, filter에 대해 알아보겠습니다.
세 개 모두 ECMAScript 2015 (ES6)에서 추가된 메서드입니다. 그리고 배열(Array) 타입에 사용하며, 각 요소들을 파라미터로 받아 작동한다는 공통점도 있지요.
그렇다면 어떤 점에서 차이점이 있고, 어떤 상황에서 사용하는 게 좋을까요?
map()은 배열 각 요소에 대하여 주어진 함수를 수행한 결과를 모아 새로운 배열을 반환하는 메서드입니다.
섭씨-> 화씨, 숫자 -> 문자 등 요소들에게 일괄적으로 함수를 적용하고 싶을 때 사용하기 적합합니다.
사용 방법은 아래 예제처럼 매개변수로 배열의 인자를 받고, 해당 인자에 어떤 변화를 준 뒤 반환하는 식으로 사용합니다.
const numbers = [1, 2, 3, 4, 5];
const numbersMap = numbers.map(val => val * 2);
console.log(numbersMap);
// [ 2, 4, 6, 8, 10 ]
위에서는 배열의 인자, val이라는 하나의 매개 변수만 사용했지만 사실 map은 3개의 매개변수를 갖고 있습니다. 나머지 2개 매개변수인 index, array는 필수 입력이 아닙니다.
매개변수의 이름과 설명은 아래와 같습니다.
value : 현재 요소
index : 요소의 인덱스
array : map()을 호출한 원본 배열 (여기서는 numbers 배열이 되겠죠?)
const numbers = [1, 2, 3];
const numbersMap = numbers.map((value, index, array) => {
console.log(value, index, array);
return value * 2;
});
console.log(numbersMap);
// 1 0 [ 1, 2, 3 ]
// 2 1 [ 1, 2, 3 ]
// 3 2 [ 1, 2, 3 ]
// [ 2, 4, 6 ]
filter()는 배열 각 요소에 대하여 주어진 함수의 결괏값이 true인 요소를 모아 새로운 배열을 반환하는 메서드입니다.
map이 내부 함수의 리턴 값이 문자, 숫자, 배열 등으로 다양한 타입이 가능하다면, filter는 오직 boolean 타입만 반환한다는 점이 특징입니다. 그리고 리턴 값이 true인 경우에만 배열에 추가하기 때문에 중복 제거처럼 조건에 맞는 특정 요소들만 새 배열에 넣고 싶은 경우에 사용하면 적합합니다.
아래 코드는 문자의 길이가 5보다 큰 과일들만 배열에 담는 예제입니다.
const fruits = ['Apple', 'Banana', 'Lemon', 'Watermelon'];
const fruitsFilter = fruits.filter(word => word.length > 5);
console.log(fruitsFilter);
// [ 'Banana', 'Watermelon' ]
filter도 map과 마찬가지로 3개의 매개변수를 갖고 있습니다. index와 array는 필수 입력 요소가 아니며, 매개변수의 이름과 설명은 아래와 같습니다.
value : 현재 요소
index : 요소의 인덱스
array : filter()을 호출한 원본 배열
const fruits = ['Apple', 'Banana', 'Lemon'];
const fruitsFilter = fruits.filter((value, index, array) => {
console.log(value, index, array);
return value.length > 5;
});
console.log(fruitsFilter);
// Apple 0 [ 'Apple', 'Banana', 'Lemon' ]
// Banana 1 [ 'Apple', 'Banana', 'Lemon' ]
// Lemon 2 [ 'Apple', 'Banana', 'Lemon' ]
// [ 'Banana' ]
reduce 메서드는 위에서 알아본 두 메서드보다 이해하기 조금 어렵지만, 그만큼 알고 나면 다양한 상황에서 활용할 수 있는 강력한 메서드입니다.
우선 reduce()는 배열 각 요소에 대하여 reducer 함수를 실행하고, map과 filter와 달리 배열이 아닌 하나의 결괏값을 반환한다는 차이점이 있습니다. 먼저 예시부터 볼까요?
const numbers = [1, 2, 3, 4];
const numbersSum = numbers.reduce((acc, cur) => {
console.log(acc, cur);
return acc + cur;
});
console.log(numbersSum);
// 1 2
// 3 3
// 6 4
// 10
위의 예시는 reduce 함수에서 값을 계속 누적하여 갖고 있는 누산기인 acc와 현재 요소인 cur를 매개변수로 하여 모든 요소의 합을 반환하는 로직입니다.
console.log(acc, cur)의 결괏값을 차례로 살펴보면 어떻게 계산이 누적되고 있는지 알 수 있습니다.
누산기라는 개념이 생소할 수 있는데 간단하게 표현하자면 reduce 함수를 수행하면서 생기는 값을 임시적으로 보관하는 형태입니다. 만약 reduce 함수를 이용해 모든 배열을 더한 값을 알고 싶다면 0번째, 1번째... n번째로 요소를 계산한 값을 갖고 있는 거죠.
또한 reduce 에는 초기값이라는 생소한 값을 입력할 수 있습니다. 아래 예시의 경우 함수의 끝에 10이라는 숫자가 추가된 걸 볼 수 있는데요, 이처럼 초기값은 숫자부터 배열까지 다양한 값을 입력할 수 있습니다.
초기값을 입력하게 되면 배열의 첫 번째 요소부터가 아니라 초기값부터 계산이 시작됩니다. 아까와는 달리 맨 첫 번째 acc의 값이 10이 된 게 보이시나요?
const numbers = [1, 2, 3, 4];
const numbersSum = numbers.reduce((acc, cur) => {
console.log(acc, cur);
return acc + cur;
}, 10);
console.log(numbersSum);
// 10 1
// 11 2
// 13 3
// 16 4
// 20
그리고 예시에서 사용한 2가지 매개변수를 포함하여 총 4개의 매개변수를 가지고 있습니다. 나머지 2개인 currentIndex와 array는 필수 입력이 아닙니다.
accumulator : 리턴한 값을 저장하는 변수. 초기값을 지정한 경우에는 초기값부터 시작.
currentValue : 현재 요소
currentIndex : 요소의 인덱스
array : map()을 호출한 원본 배열
const numbers = [1, 2, 3, 4];
const numbersSum = numbers.reduce((accumulator, currentValue, currentIndex, array) => {
console.log(accumulator, currentValue, currentIndex, array);
return accumulator + currentValue;
}, 10);
console.log(numbersSum);
// 10 1 0 [ 1, 2, 3, 4 ]
// 11 2 1 [ 1, 2, 3, 4 ]
// 13 3 2 [ 1, 2, 3, 4 ]
// 16 4 3 [ 1, 2, 3, 4 ]
// 20
reduce는 매개변수로 들어가는 누산기라는 개념이 낯설고, 문법도 다른 메서드에 비해 까다롭기 때문에 초반에 이해하기엔 비교적 어렵습니다. 하지만 그만큼 한 번 익혀두면 유용하게 사용하는 메서드이기도 합니다.
특히 map, filter와 같은 함수형 메서드를 reduce만으로도 구현할 수 있습니다.
아래는 위에서 작업한 map, filter 예제를 reduce를 사용하여 재작성한 코드입니다.
// map처럼 쓰기
const numbers = [1, 2, 3, 4, 5];
const numbersReduce = numbers.reduce((acc, cur) => {
acc.push(cur * 2);
return acc;
}, []);
console.log(numbersReduce);
// [ 2, 4, 6, 8, 10 ]
// filter처럼 쓰기
const fruits = ['Apple', 'Banana', 'Lemon', 'Watermelon'];
const fruitsReduce = fruits.reduce((acc, cur) => {
if (cur.length > 5)
acc.push(cur);
return acc;
}, []);
console.log(fruitsReduce);
// [ 'Banana', 'Watermelon' ]
이처럼 reduce()는 활용도가 높은 함수이기 때문에 다양한 목적으로 사용할 수 있습니다. 활용 방법은 공식 문서에서 자세히 나와있으니 한 번 보시는 것을 추천합니다.
오늘은 배열 타입에서 사용하는 세 함수 reduce(), filter(), map()에 대해 알아보았습니다.
낯선 개념이라 초반에는 이해하기 어렵겠지만 직접 코드를 작성하며 내 것으로 만들기 위해 노력한다면, 어느 순간 긴 for문을 단 한 줄로 줄이고 있는 자신을 발견할 수 있으리라 생각합니다.
그럼 다음 포스팅에서 뵙겠습니다.
Array.prototype.reduce() - JavaScript - MDN - Mozilla
Array.prototype.map() - JavaScript - MDN - Mozilla