개념/배경
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와의 역할 분리가 핵심 포인트 데이터 변환 중심 로직에서 선언적이고 예측 가능한 코드 작성에 적합함