[JavaScript] map, forEach, reduce

비얌·2022년 11월 11일
0
post-thumbnail

서론

널리 사용되는 Airbnb 자바스크립트 컨벤션을 보면, for-in과 for-of와 같은 루프 대신 map, forEach, reduce과 같은 자바스크립트의 함수를 사용하라고 권장하고 있다.

(하지만 Airbnb 자바스크립트 컨벤션이 '클린코드'인가에 대해서는 이견이 있다. 2. forEach 부분에서 자세히 설명함)


관련된 부분 - 영문 Airbnb 자바스크립트 컨벤션


관련된 부분 - 한글 Airbnb 자바스크립트 컨벤션

Airbnb Javascript 컨벤션(영문)
Airbnb Javascript 컨벤션(한글)


현재 진행 중인 우테코 프리코스에서도 for-in과 for-of 대신 자바스크립트의 내장 함수를 쓰라는 리뷰를 많이 받았어서, 이번 포스팅에서는 map, forEach, reduce 함수에 대하여 알아보려고 한다.

사실 이전에 [자바스크립트] 배열 내장함수에 대해 알아보자 포스팅을 통해 자바스크립트 내장 함수에 대해 공부한 적이 있다. 하지만 거의 이론 위주만 알아보고 실제로 코딩을 해보지는 않아서 이 함수들을 실제로 쓰지 못했던 것 같다.

그래서 이번 포스팅에서는 실제로 map, forEach, reduce를 여러 예시를 들어가며 직접 코딩하며 익혀보려고 한다.



map, forEach, reduce

1. map

구문

  • arr.map(callback(currentValue[, index[, array]])[, thisArg])
  • 배열.map((요소, 인덱스, 배열) => { return 요소 })

설명

  • for문과 마찬가지로 반복적인 기능을 수행할 때 사용한다.
  • map은 배열 안의 각 원소를 변환할 때 사용되며, 이 과정에서 새로운 배열이 만들어진다.
  • forEach()와 다르게 리턴값이 있다.
  • 반복문을 돌며 배열 안의 요소들을 1대1로 짝지어 준다.
  • 일반 함수 형태와 화살표 함수 형태로 나타낼 수 있다.
    const arr = [10, 20, 30];

    //일반 함수 형태
    arr.map(function(item, index) {
        console.log(index+"번 값", item);
    });

    //화살표 함수 형태
    arr.map((item, index) => {
        console.log(index+"번 값", item);
    });

	// "0번 값"
    // 10
    // "1번 값"
    // 20
    // "2번 값"
    // 30
    // "3번 값"
    // 40

예시

(1) 각 원소 출력하기

const arr = [1, 2, 3, 4, 5];
const result = arr.map((e) => {
  return e;
})
console.log(result); // [1, 2, 3, 4, 5]
const arr = [1, 2, 3, 4, 5];
const result = arr.map(e => e)
console.log(result); // [1, 2, 3, 4, 5]

(2) 각 원소에 1 더해서 출력하기

const arr = [1, 2, 3, 4, 5];
const result = arr.map((e) => {
  return e + 1;
})
console.log(result); // [2, 3, 4, 5, 6]

const result = arr.map(e => e + 1)
console.log(result); // [2, 3, 4, 5, 6]

(3) 각 원소 제곱해서 출력하기

const arr = [1, 2, 3, 4, 5];
const result = arr.map(e => e * e)
console.log(result); // [1, 4, 9, 16, 25]

(4) 제곱근 구하기

map 안에는 함수가 오기만 하면 된다.(Math.sqrt)

const arr = [1, 2, 3, 4, 5];

const squares1 = arr.map(Math.sqrt);
const squares2 = arr.map(e => Math.sqrt(e));

console.log(squares1); // [1, 1.4142135623730951, 1.7320508075688772, 2, 2.23606797749979]
console.log(squares2); // [1, 1.4142135623730951, 1.7320508075688772, 2, 2.23606797749979]

(5) 배열에 들어가 있는 값 중 특정 값만 추출해 새로운 형태의 배열을 만들기

const users = [
    { name: 'YD', age: 22 },
    { name: 'Bill', age: 32 },
    { name: 'Andy', age: 21 },
    { name: 'Roky', age: 35 },
];

const ages = users.map(user => user.age);

console.log(ages); // [22, 32, 21, 35]

(6) 특정 요소만 재정의하기

const users = [
    { name: 'YD', age: 22 },
    { name: 'Bill', age: 32 },
    { name: 'Andy', age: 21 },
    { name: 'Roky', age: 35 },
];

const newUsers = users.map(user => {
  if (user.name === 'YD') {
    return { ...user, age: 18 }; // ...user가 없으면 name이 들어가지 않음
  }
  return { ...user }; // 이게 없으면 name이 'YD'인 부분만 정의되고 나머지는 undefined가 됨
})

console.log(newUsers);

// [[object Object] {
//   age: 18,
//   name: "YD"
// }, [object Object] {
//   age: 32,
//   name: "Bill"
// }, [object Object] {
//   age: 21,
//   name: "Andy"
// }, [object Object] {
//   age: 35,
//   name: "Roky"
// }]


2. forEach

구문

  • arr.forEach(callback(currentvalue[, index[, array]])[, thisArg])
  • 배열.forEach((요소, 인덱스, 배열) => { return 요소 })

설명

  • for문과 마찬가지로 반복적인 기능을 수행할 때 사용한다.
  • for-of 문과 거의 다를 바가 없다.
  • map 메서드와 다르게 따로 콜백 함수가 return 하는 값을 따로 모아서 어떤 처리를 하는 과정이 없다. 그래서 메서드를 호출한 코드를 함수에 할당하면 undefined가 할당된다.
  • 변수에 할당하기보다는 반복문이나 조건문과 같이 그냥 바로 호출되는 곳에 사용하는 것이 일반적이다.

예시

(1) 각 원소 출력하기

forEach를 사용할 때는 함수를 따로 변수에 할당하지 않는다.

const students = ['John', 'Sara', 'Jack'];

students.forEach(e => {
  console.log(e);
});

// John
// Sara
// Jack

map으로 각 원소를 출력했을 때와 비교해보자.

const arr = [1, 2, 3, 4, 5];
const result = arr.map((e) => {
  return e;
})
console.log(result); // [1, 2, 3, 4, 5]

(2) 실제로 지적받은 부분 forEach로 고쳐보기

우테코 프리코스를 하며 1주 차 미션을 제출하고 받은 코드 리뷰에서 forEach가 언급된 적이 있다. for 대신 forEach를 써보라는 조언이었다.

지적받은 내 코드는 아래와 같다.

const nameList = ['제이엠', '이엠지', '제제이', '엠지엠', '엘렐레'];
const twoLetterList = ['제이', '엠지', '지엠'];
const countList = [0, 0, 0];

for (let i of nameList) {
    for (let j = 0; j < twoLetterList.length; j++) {
      let k = twoLetterList[j];
      if (i.indexOf(k) !== -1) {
        countList[j] = countList[j] + 1;
      }
    }
  }

console.log(countList);

리뷰를 해주신 민재 님의 코드를 한번 살펴보았다. forEach로 각 단어를 탐색하며 순회하고 있다.

function checkDuplicatePattern(wordSet, patterns, emailSet, email) {
  wordSet.forEach((word) => {
    if(!patterns[word]) {
      patterns[word] = email;
      return;
    }
 });
}

forms.forEach(([email, nickName]) => {
  const wordSet = makeWordSet(nickName);
  checkDuplicatePattern(wordSet, patterns, emailSet, email);
});

나도 forEach로 코드를 바꿔보기로 했다. 바꾸면 아래와 같다. 이게.. 그냥 for문을 쓰는 것보다 좋다는 거겠지?

const nameList = ['제이엠', '이엠지', '제제이', '엠지엠', '엘렐레'];
const twoLetterList = ['제이', '엠지', '지엠'];
let countList = [0, 0, 0];

nameList.forEach((name) => {
  twoLetterList.forEach((twoLetter, index) => {
    if (name.indexOf(twoLetter) !== -1) {
      console.log(index+'번째에'+twoLetter+'가 겹칩니다');
      countList[index] = countList[index] + 1;
    }
  })
})

console.log(countList);

🔥 질문과 답변

forEach로 바꿨을 때 어떤 점이 좋은 건지 모르겠어서 개발자 커뮤니티에 질문을 올려보았다.


네 분이 답변해주셨고, 예상 외의 답변들이 와서 조금 당황했는데.. 정리해보면

<그렇게 바꿔서 좋을 거 없다는 것으로 의견이 존재함>이었다.

자세한 답변은 아래와 같다.

  • for을 forEach 등으로 쓰는 것에 대하여 좋은 거 없고 for of를 더 권장하는 의견이 많을 것이다.
  • 하지만 인덱스까지 같이 필요한 경우에는 forEach가 더 편하긴 함. 단순 순회는 for...of가 낫고 인덱스 필요하면 forEach를 써서 두 번째 인자를 받는 것이 낫다.
  • map, filter는 성능이 정말 중요한 경우가 아니면 클린 코드가 맞고, reduce는 실질적으로 가독성이 좋아지는지는 약간 논란의 여지가 있지만 맞다고 치지만, forEach는 취향이 맞다.
  • for 대신 forEach랑 reduce가 낫다는 주장에 전혀 동의하지 않는다.
  • Airbnb 자바스크립트 컨벤션은
    "클린코드" (X)
    "한 회사의 전통" (O)
  • 신규 프로젝트에서 에어비엔비 룰 사용을 엄격히 금지해야 한다.
  • forEach를 사용하면 어떤 배열을 순회하는지 바로 알 수 있어서 좋은 것 같다

Airbnb 자바스크립트 컨벤션이 완전히 옳은 클린코드인줄 알았는데, 이렇게 부정적인 의견이 있어서 놀랐다. 우테코에서 Airbnb 컨벤션을 지키라고 하긴 하지만, 관련해서 부정적인 의견이 있다는 것도 알고가야겠다!



3. reduce

구문

  • arr.reduce(callback[, initialValue])
  • 배열.reduce((누산기, 현재요소, 인덱스, 배열) => { return 요소 })

설명

  • 배열의 각 요소를 순회하며 callback함수의 실행 값을 누적하여 하나의 결과값을 반환한다.
  • callback은 4가지 인수를 가진다.
    1. accumulator - accumulator는 callback함수의 반환값을 누적한다.
    2. currentValue - 배열의 현재 요소
    3. index(Optional) - 배열의 현재 요소의 인덱스
    4. array(Optional) - 호출한 배열
  • callback함수의 반환 값은 accumulator에 할당되고 순회중 계속 누적되어 최종적으로 하나의 값을 반환한다.
  • initialValue는 최초 callback함수 실행 시 accumulator 인수에 제공되는 값이다.
  • 초기값(initialValue는)을 제공하지 않을경우 배열의 첫 번째 요소를 사용하고, 빈 배열에서 초기값이 없을 경우 에러가 발생한다.

예시

(1) 연산 순서 확인하기

const arr = [1, 2, 3, 4, 5];

function callbackFunc(accumulator, currentValue, Index, array) {
  console.log("acc: " + accumulator + ", cur: " + currentValue
    + ", curIndex: " + Index + ", array: " + array);
  return accumulator + currentValue;
}

const result = arr.reduce(callbackFunc, 0);

console.log(result);

// "acc: 0, cur: 1, curIndex: 0, array: 1,2,3,4,5"
// "acc: 1, cur: 2, curIndex: 1, array: 1,2,3,4,5"
// "acc: 3, cur: 3, curIndex: 2, array: 1,2,3,4,5"
// "acc: 6, cur: 4, curIndex: 3, array: 1,2,3,4,5"
// "acc: 10, cur: 5, curIndex: 4, array: 1,2,3,4,5"
// 15

(2) 배열의 모든 값 더하기

const numbers = [1, 2, 3, 4, 5];

const sumNumber = numbers.reduce((accumulator, currentNumber) => accumulator + currentNumber, 0);

console.log(sumNumber); // 15


4. 정리(추가함)

map, forEach, reduce를 재미있게 정리한 것이 있어 가져와봤다.


map([🌽, 🐮, 🐔], cook)
=> [🍿, 🍔, 🍳]

forEach([🍿, 🍔], eat)
=> undefined

reduce([🍿, 🍳], eat)
=> 💩


실제로 '1, 2, 3, 4, 5, 6'[1, 2, 3, 4, 5, 6]으로 바꿔야 할 일이 있어서 처음에는 forEach를 사용하려고 했다. 하지만 forEach는 undefined를 반환하기 때문에 의도대로 값이 나오지 않았고, map을 써서

'1, 2, 3, 4, 5, 6'.split(',').map(number => Number(number));라고 쓰는게 맞았다.

map과 forEach가 비슷한 줄 알았는데, 전혀 다르다는 걸 알 수 있었다.



🐹 회고

이번 포스팅에서는 자바스크립트의 내장 함수 map, forEach, reduce에 대해 알아보았다.

가장 유명한 Airbnb 자바스크립트 컨벤션에서는 for-of, for-in 대신 forEach, reduce, map 등을 사용하라고 권장하고 있다. 그리고 우테코에서는 Airbnb 자바스크립트 컨벤션을 지키라고 하고 있어서 코드 리뷰 스터디 팀원들은 이를 참고하여 내 코드에 for 대신 forEach를 사용하라는 리뷰를 남겼다.

그래서 이 포스팅을 시작한 건데..! for 대신 forEach가 낫지 않다는 의견들이 있어서 놀랐다(인덱스가 필요한 때를 빼고). 하지만 항상 궁금했던 자바스크립트의 내장 함수에 대해 한번 더 짚고 넘어갈 수 있어서 좋은 경험이었다!

profile
🐹강화하고 싶은 기억을 기록하고 공유하자🐹

2개의 댓글

comment-user-thumbnail
2022년 11월 13일

헉..! forEach에 관해서 다시 한 번 생각하게 되네요.. 좋은 글 감사합니다!

1개의 답글