logo

map, reduce, filter

Chapter 19

41 조회

0 추천

1,219 단어

7분 예상

2024. 08. 19. 게시

luasenvy 작성

CC BY-NC-SA 4.0

forEach

const arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];

arr.forEach((element, index) => {
  console.info(element, index);
});

for ( let i = 0, ilen = arr.length; i < ilen; i++ )
  console.info(arr[i], i);

forEach() 함수는 Array 객체가 제공해주는 내장함수로 배열의 각 요소들을 차례로 순회하는 함수다. 위 두개의 함수가 수행하는 의미하는 것은 완전하게 동일하다. 배열을 순회하며 넘겨준 함수를 한 번씩 실행시키고 종료하는 아주 단순한 함수다. for 명령의 경우 블록 내에서 break 구문을 통해 반복을 조기종료시킬 수 있지만 forEach의 경우 오류를 발생시키는 것 외에는 중지할 방법이 없으므로 적절하게 사용해야 한다.

const arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];

// forEach는 Promise를 대기 하지 않음
arr.forEach(async (el, i) => await asyncFn(el, i));

// for 문을 사용하여 동기식으로 구현하여야함.
for ( let i = 0, ilen = arr.length; i < ilen; i++ )
  await asyncFn(arr[i], i);

// Promise.all 을 활용한 방법
// forEach 대신 map 함수를 활용하여 모든 순회 함수를 배열로 반환시킨다.
// Promise.all은 이 프로미스 배열을 인자로 넘겨받고 순서에 상관없이 모든 프로미스가 종료될때까지 await 구문으로 대기시킬 수 있다.
await Promise.all(
  arr.map(async (element, i) => asyncFn(arr[i], i))
);

이런 피할 수 없는 경우는 후에 프로미스를 다루면서 또 마주치게 되는데 두가지 문법이 모두 동일해 보이지만 forEach는 인자로 넘긴 함수가 Promise인지 확인하지 않고 무조건 실행만 한다. 그래서 프로미스를 정지하지 않고 모든 순회마다 함수를 실행시킬 뿐이다. 응답에 대한 순서도 보장되지 않기 때문에 await 구문을 활용하여 Promise를 대기시키도록 명시해야 한다. 또는 위 예시 하단처럼 Promise.all과 같은 몇 가지 대안이 있다. 작동 방식에 따라서 상황에 맞게 사용해야 한다.

map / filter

const arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];

const multiply = arr.map((element) => element * 2); // [20, 18, 16, 14, 12, 10, 8, 6, 4, 2]
const evens = arr.filter((element) => element % 2 === 0); // [10, 8, 6, 4, 2]

모두 배열을 순회하면서 수행하는 작업들로 약간씩의 차이가 있을 뿐 위의 forEach()의 동작을 이해했다면 나머지는 식은 죽 먹기이다. mapfilter 모두 반환형을 가지고 있다. map()은 모든 순회가 종료되면 매 순회마다 반환된 값들을 요소로하는 배열을 반환한다. filter()는 말 그대로 걸러내주는 함수로 모든 순회가 종료되면 매 순회마다 반환된 값이 true 였던 요소만을 가지는 배열을 반환한다.

reduce

모두 배열을 조작할 때 사용하는 대표적인 함수들이지만 reduce 하나만 알면 배열과 관련된 모든걸 할 수 있어서 특별히 문단을 분리하였다. reduce도 다른 함수들과 마찬가지로 배열의 모든 요소를 순회하면서 각 요소마다 한 번씩 인자로 넘긴 함수를 실행한다.

const arr = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];

const sum = arr.reduce((accumulator, element) => accumulator + element, 0);
const max = arr.reduce((accumulator, element) => accumulator < element ? element : accumulator, 0);
const min = arr.reduce((accumulator, element) => accumulator > element ? element : accumulator, 0);

reduce 함수는 인자를 두개 필요로 하는데 매 순회마다 실행할 함수와 초기값이다. reduce 함수는 인자로 넘겨준 함수로 accumulator 인자를 매 순회마다 이전에 반환된 값을 넘겨준다. accumulator를 통하여 작업을 이어나갈 수 있는 것이다. 배열의 첫번째 요소를 순회할 때에는 이 accumulator의 값이 없기 때문에 두번째 인자로 넘겨준 초기값이 할당되어 있다.

이 것만 이해한다면 배열로 구현할 수 있는 모든 작업을 할 수 있다. 이전 작업을 이어서 할 수 있기 때문에 배열 데이터를 최소값, 최대값, 평균, 합계와 같이 집산할때 많이 사용된다. 그러나 단순히 숫자 계산을 위하여 사용되지도 않는다.

const multiply = arr.reduce((accumulator, element) => accumulator.concat(element * 2), []);
const evens = arr.reduce((accumulator, element) => accumulator % 2 === 0 ? accumulator.concat(element) : accumulator, []);

이런식으로 사용하면 map(), filter() 함수와 완전하게 동일한 작동을 하게 만들 수도 있다. 실제로도 다양한 라이브러리들이 reduce 하나를 재활용하여 다양한 형태의 배열 유틸리티를 구현하기도 한다.