개념/배경

Array.prototype.map은 배열의 각 요소에 대해 콜백을 호출해 변환된 결과로 새 배열을 만드는 메서드임 원본 배열 불변 유지, 동일 길이의 새 배열 생성이 핵심 특징임

기본 문법

const newArray = array.map((element, index, array) => {
  return element;
});
  • element 현재 요소
  • index 현재 인덱스
  • array 원본 배열 참조

콜백은 요소 수만큼 호출되고, 콜백의 반환값이 새 배열의 같은 위치에 배치됨

사용 예시

숫자 배열 변환

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((n) => n * 2);
// [2, 4, 6, 8, 10]

객체 배열에서 특정 속성 추출

const users = [{ name: "Alice" }, { name: "Bob" }];
const names = users.map((u) => u.name);
// ['Alice', 'Bob']

인덱스 활용

const arr = [10, 20, 30];
const result = arr.map((n, i) => n + i);
// [10, 21, 32]

중첩 배열 변환

const nested = [
  [1, 2],
  [3, 4],
];
const sums = nested.map((a) => a.reduce((x, y) => x + y, 0));
// [3, 7]

원본 배열과 불변성

map은 원본 배열을 수정하지 않음

const numbers = [1, 2, 3];
const squared = numbers.map((n) => n ** 2);
// numbers -> [1, 2, 3]
// squared -> [1, 4, 9]

null/undefined와 빈 슬롯 처리 주의

  • 명시적 undefined 값이나 null 값은 콜백이 호출됨
  • 반면, 배열의 빈 슬롯(hole)은 콜백이 호출되지 않음. 새 배열에도 동일한 위치의 빈 슬롯이 유지됨
// 명시적 undefined는 콜백 호출됨
const a = [1, undefined, 3];
const r1 = a.map((x) => (x === undefined ? "empty" : x * 2));
// [2, 'empty', 6]

// 빈 슬롯은 콜백 미호출, 그대로 빈 슬롯 유지됨
const b = [1, , 3]; // sparse array
const r2 = b.map((x) => "seen");
// r2는 ['seen', , 'seen'] 형태. 가운데는 여전히 빈 슬롯

이 차이를 혼동하면 데이터 품질 이슈 발생 가능. sparse array를 다루는 경우 사전에 채우기 또는 filter와의 조합 고려 권장

forEach와의 차이

  • map 새 배열 반환, 변환 목적
  • forEach 반환값 없음, 부수 효과 목적
const nums = [1, 2, 3];
nums.forEach((n) => console.log(n * 2)); // 출력만 수행
const doubled = nums.map((n) => n * 2); // [2, 4, 6]

장점

  • 불변성 유지
  • 선언적 데이터 변환
  • 체이닝에 유리, 가독성 향상

주의와 팁

  • 콜백은 순수 함수 지향. 부수 효과는 forEach 사용 권장
  • map은 배열 길이를 기준으로 동작. 순회 중 새 요소를 push해도 방문하지 않음, 삭제된 요소는 방문되지 않음
  • 배열 요소가 배열을 반환하는 변환이 많다면 flatMap 고려. map 후 flat보다 간결한 선택지
  • 성능 민감 구간에서는 불필요한 중간 배열 생성을 피하도록 파이프라인 재검토 권장

정리

map은 각 요소를 변환해 동일 길이의 새 배열을 만드는 표준 도구 원본 불변, 명시적 undefined와 빈 슬롯 처리 차이, forEach와의 역할 분리가 핵심 포인트 데이터 변환 중심 로직에서 선언적이고 예측 가능한 코드 작성에 적합함

참고자료