JavaScript - map, reduce, forEach (★★★★★)

bunny.log·2022년 10월 28일
0

forEach

forEach 메서드도 배열을 순회하기 위해 사용되지만, 기본적인 for 반복문과는 다른 방식으로 함수를 사용합니다.

forEach 메서드는 다음 매개변수(parameter)와 함께 배열의 각 요소에 적용하게 될 콜백 함수(callback function)를 전달합니다.

  1. Current Value (명명된 매개변수) - 처리할 현재 요소
  2. Index (선택적 매개변수) - 처리할 현재 요소의 인덱스
  3. Array (선택적 매개변수) - forEach 메서드를 호출한 배열
배열.forEach((Current Value , Index, Array ) => { ... })

참고로, forEach의 상위호환이라고 해서 배열의 요소들을 순회할 때 그냥 무조건 map 메서드를 활용하는 경우를 본 적이 있었는데,
개인적으로는 return 값으로 새로운 배열이 필요하지 않는 경우라면 forEach 메서드를 활용하는 것이 옳다고 생각한다.

map 메서드

배열.map((요소, 인덱스, 배열) => { return 요소 });

map의 기본 원리는 간단합니다.
반복문을 돌며 배열 안의 요소들을 1대1로 짝지어 주는 것입니다. 그래서 이름이 map입니다. 매핑한다고 표현하죠. 어떻게 짝지어줄 것인가 정의한 함수를 메서드의 인자로 넣어주면 되는 것입니다.

일단 주어진 배열을 그대로 매핑해봅시다.

const oneTwoThree = [1, 2, 3];
let result = oneTwoThree.map((v) => {
  console.log(v);
  return v;
});

// 콘솔에는 1, 2, 3이 찍힘
oneTwoThree; // [1, 2, 3]
result; // [1, 2, 3]
oneTwoThree === result; // false

반복문으로 요소를 순회(1, 2, 3 순서로)하면서 각 요소를 어떻게 짝지어줄지 알려줍니다. 함수가 그냥 return v를 하기 때문에 같은 값을 그대로 짝짓습니다.

oneTwoThree === result; // false

알아둘 점은, map을 실행하는 배열과 결과로 나오는 배열이 다른 객체라는 것입니다. 기존 배열을 수정하지 않고 새로운 배열을 만들어냅니다. 단, 배열 안에 객체가 들어있는 경우, 객체는 공유됩니다.

let result = oneTwoThree.map((v) => {
  if (v % 2) {
    return '홀수';
  }
  return '짝수';
});
result; // ['홀수', '짝수', '홀수']

규칙적인 배열만 반환할 수 있는게 아니라, 함수 안에 적어준대로 반환할 수 있기 때문에 자유도가 높습니다.

reduce

배열.reduce((누적값, 현잿값, 인덱스, 요소) => { return 결과 }, 초깃값);

reduce 초깃값을 적었을 때와 안 적었을 때의 차이

초기값 적용

const oneTwoThree = [1, 2, 3];
let result = oneTwoThree.reduce((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
}, 0);
// 0 1 0
// 1 2 1
// 3 3 2
result; // 6

acc(누적값)이 초깃값인 0부터 시작해서 return하는대로 누적되는 것을 볼 수 있습니다.
초깃값을 적어주지 않으면 자동으로 초깃값이 0번째 인덱스의 값이 됩니다.

초기값 적용하지 않음

const oneTwoThree = [1, 2, 3];
let result = oneTwoThree.reduce((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
});
// 1 2 1
// 3 3 2
result; // 6 

reduceRight는 reduce와 동작은 같지만 요소 순회를 오른쪽에서부터 왼쪽으로 한다는 점이 차이입니다.

const oneTwoThree = [1, 2, 3];
let result = oneTwoThree.reduceRight((acc, cur, i) => {
  console.log(acc, cur, i);
  return acc + cur;
}, 0);
// 0 3 2
// 3 2 1
// 5 1 0
result; // 6

심화

여기까지는 흔한 덧셈 예제이지만... reduce는 덧셈 곱셈만을 위한 것이 아닙니다. 초깃값을 활용하여 어떤 일을 할 수 있는지 알아보겠습니다. map과 filter과 같은 함수형 메서드를 모두 reduce로 모두 구현할 수 있습니다.

위의 map 예제를 reduce로 만들어보겠습니다.

result = oneTwoThree.reduce((acc, cur) => {
  acc.push(cur % 2 ? '홀수' : '짝수');
  return acc;
}, []);
result; // ['홀수', '짝수', '홀수']

초깃값을 배열로 만들고, 배열에 값들을 push하면 map과 같아집니다. 이를 응용하여 조건부로 push를 하면 filter와 같아집니다. 다음은 홀수만 필터링하는 코드입니다.

result = oneTwoThree.reduce((acc, cur) => {
  if (cur % 2) acc.push(cur);
  return acc;
}, []);
result; // [1, 3]

이와 같이 sort, every, some, find, findIndex, includes도 다 reduce로 구현 가능합니다. reduce가 이들 모두의 아버지였던 것입니다. ㅠㅠ

reduce는 비동기 프로그래밍을 할 때에도 유용합니다.

const promiseFactory = (time) => {
  return new Promise((resolve, reject) => {
    console.log(time); 
    setTimeout(resolve, time);
  });
};
[1000, 2000, 3000, 4000].reduce((acc, cur) => {
  return acc.then(() => promiseFactory(cur));
}, Promise.resolve());
// 바로 1000
// 1초 후 2000
// 2초 후 3000
// 3초 후 4000

초깃값을 Promise.resolve()로 한 후에, return된 프로미스에 then을 붙여 다음 누적값으로 넘기면 됩니다. 프로미스가 순차적으로 실행됨을 보장할 수 있습니다.

반복되는 모든 것에는 reduce를 쓸 수 있다는 것을 기억하시면 됩니다.

map과 reduce 외에도, 배열의 메서드인 sort, filter, every, some, find, findIndex, includes 정도는 알아두시면 좋습니다. 오늘 reduce만 있어도 다른 메서드들을 다 구현할 수 있다는 것을 배웠기 때문에, 다른 메서드를 까먹으면 reduce로 구현하시면 됩니다!

https://www.zerocho.com/category/JavaScript/post/5acafb05f24445001b8d796d

https://www.freecodecamp.org/korean/news/javascript-foreach-how-to-loop-through-an-array/

profile
나를 위한 경험기록

0개의 댓글