개요
Array.prototype.map은 배열의 각 요소에 대해 지정한 콜백을 호출하고 그 반환값으로 새 배열을 만드는 메서드임 원본 배열은 변경하지 않으며 콜백의 부수효과로만 변형이 일어날 수 있음 희소 배열의 빈 슬롯은 유지되며 콜백은 값이 존재하는 인덱스에만 호출됨
구문
arr.map(callback(currentValue[, index[, array]])[, thisArg])
- callback 현재 요소를 변환해 새 요소를 생성하는 함수
- currentValue 현재 요소 값
- index 선택 현재 요소의 인덱스
- array 선택 map을 호출한 원본 배열
- thisArg 선택 콜백 실행 시 this로 사용할 값
반환값
각 요소에 대해 callback을 실행한 결과로 구성된 새 배열 반환 원본 배열과 길이는 동일하고 희소성도 보존됨
동작 원리와 특징
- 순회 범위 고정 첫 콜백이 호출되기 전에 배열의 길이 스냅샷이 결정됨
- 추가와 삭제 map 진행 중 추가된 요소는 방문하지 않음, 방문 전 삭제된 요소는 건너뜀
- 값 읽기 시점 요소 값은 방문 시점의 최신 값이 콜백에 전달됨
- 희소 배열 처리 값이 없는 인덱스는 콜백 미호출, 결과 배열에서도 동일 인덱스가 빈 슬롯으로 남음
- this 바인딩 thisArg가 주어지면 콜백의 this로 사용되고, 없으면 undefined가 기본값으로 들어감. 최종 this는 일반적인 this 바인딩 규칙을 따름
- 불변성 보장 map 자체는 원본을 수정하지 않음. 단 콜백 내부에서 원본을 바꾸면 그 영향은 순회 타이밍에 따라 결과에 반영될 수 있음
언제 map을 쓰는가
- 변환 목적 각 요소를 다른 값으로 치환해 새 배열로 만들 때 사용
- forEach와 비교 forEach는 반환값이 없고 부수효과 중심. map은 새 배열 생성 중심
- flatMap과 비교 요소를 배열로 변환하고 1단계 평탄화가 필요하면 flatMap 권장
간단 예시
- 숫자 변환
numbers.map(Math.sqrt)제곱근 배열 생성 - 객체 재구성
list.map(({ key, value }) => ({ [key]: value }))키-값 쌍을 객체로 매핑 - 문자열에도 적용 가능
Array.prototype.map.call('Hello', ch => ch.charCodeAt(0))문자 코드 배열 생성 - 유사 배열 처리
Array.from(document.querySelectorAll('select option:checked'), el => el.value)NodeList를 값 배열로 변환
까다로운 사례와 주의
- parseInt와 map의 조합 혼동 포인트
- map은 콜백에 (value, index, array)를 전달함
- parseInt는 두 번째 인자로 기수(radix)를 받음
['1','2','3'].map(parseInt)는 내부적으로parseInt('1', 0),parseInt('2', 1),parseInt('3', 2)를 호출하므로 결과가[1, NaN, NaN]이 됨- 안전한 대안
arr.map(x => parseInt(x, 10))또는arr.map(Number)사용. Number는 부동소수점과 지수표현을 숫자로 변환함
- 희소 배열 보존 의도적으로 빈 슬롯을 제거하려면 map 전에 filter로 값 존재 여부를 확인하거나 map 이후에 compaction 절차 필요
- 성능 고려 대형 배열에서 불필요한 할당을 줄이려면 변환이 필요할 때만 map 사용. 변환 없이 반복이 목적이면 for-of나 forEach가 더 적합할 수 있음
콜백 작성 팁
- 순수 함수 지향 동일 입력은 동일 출력 보장. 부수효과 최소화 권장
- 명시적 반환 값 콜백은 항상 새 값을 반환해야 함. 반환 누락 시 undefined가 채워짐
- thisArg 사용 지양 화살표 함수와 클로저를 우선 고려. 필요한 경우에만 thisArg 전달
동작 세부 규칙 요약
- 길이 스냅샷 고정 map 시작 시 len 결정, 이후 변경은 순회 범위에 영향 없음
- 요소 추가 무시 순회 중 추가된 인덱스는 미방문
- 요소 삭제 무시 방문 전 삭제된 인덱스는 미방문
- 값 갱신 반영 방문 시점에 읽은 값 사용
- 희소성 유지 원본의 빈 슬롯은 결과에도 빈 슬롯으로 유지
최소 스니펫 모음
- 제곱근 배열
const roots = [1, 4, 9].map(Math.sqrt) - 두 배 만들기
const doubled = [1, 4, 9].map(n => n * 2) - 안전한 정수 변환
const ints = ['1','2','3'].map(s => parseInt(s, 10)) - 빠른 숫자 변환
const nums = ['1.1','2.2e2'].map(Number) - NodeList 값 추출
const values = Array.from(document.querySelectorAll('select option:checked'), el => el.value) - 문자열 문자 코드
const codes = Array.prototype.map.call('Hi', ch => ch.charCodeAt(0))
폴리필 요약
- map은 ES5에서 도입됨. 매우 오래된 환경에서는 폴리필이 필요할 수 있음
- 표준 알고리즘은 this를 객체화하고 길이를 Uint32로 계산, 각 존재하는 인덱스에 대해 콜백을 call로 호출하고 결과를 동일 인덱스에 기록하는 흐름을 따름
- 자체 구현 시 다음 조건을 충족해야 함
- this가 null 또는 undefined인 경우 TypeError 발생
- callback이 함수가 아니면 TypeError 발생
- 존재하는 인덱스에만 콜백 호출, 결과 배열에 동일 인덱스로 기록
- 희소성 보존과 길이 스냅샷 규칙 준수
- 실무에선 검증된 표준 폴리필이나 런타임의 내장 구현 사용 권장
호환성
- 현대 브라우저와 일반적인 서버 런타임에서 폭넓게 지원됨. 레거시 환경 대상이 아니라면 추가 조치 불필요
베스트 프랙티스
- 목적 분리 변환은 map, 필터링은 filter, 축약은 reduce로 역할 분리
- 체이닝 설계 map → filter → reduce 등 파이프라인 가독성 유지
- 희소 배열 다루기 전처리로 빈 슬롯 제거 또는 기본값 채우기 고려
- parseInt 사용 시 radix 명시. 필요 시 Number로 단순 변환
- 배열이 아닌 대상에는 Array.from 또는 명시적 call로 map 적용
같이 보기
- Array.prototype.forEach 반복만 필요할 때 선택
- Array.prototype.flatMap 1단계 평탄화가 필요한 변환에 사용
- Array.from 유사 배열을 배열로 변환하거나 생성 시 맵 함수 제공 가능
참고 링크 요약
- 사양의 map 알고리즘과 콜백 호출 규칙은 TC39 ECMAScript 명세를 따름
- parseInt와 map의 상호작용 이슈는 잘 알려진 사례로, 콜백 인자 전달 규칙을 이해하면 쉽게 피할 수 있음
참고자료
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/map
- https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.map
- http://www.wirfs-brock.com/allen/posts/166
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/from
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach