개요

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의 상호작용 이슈는 잘 알려진 사례로, 콜백 인자 전달 규칙을 이해하면 쉽게 피할 수 있음

참고자료