- Published on
Javascript Map 객체
- Authors
- Name
- sulmo
JS에서는 해시테이블을 이용하여 최적화된 Map이라는 기본제공 객체가 존재합니다. 일반 Object를 이용하지 않고 Map이 제공되고 있는 이유를 알아봅니다.
Map 객체와 일반 JavaScript 객체(Object)의 주요 차이점
- 키(Key) 타입
- Map: 모든 타입의 값을 키로 사용 가능 (객체, 함수, 원시 타입 등)
- Object: 문자열과 Symbol만 키로 사용 가능
- 크기 확인
- Map:
size
속성으로 쉽게 크기 확인 가능 - Object:
Object.keys(obj).length
로 계산해야 함
- 순회
- Map:
for...of
루프로 직접 순회 가능 - Object:
Object.keys()
,Object.entries()
등을 사용해야 함
- 성능
- Map: 빈번한 추가/삭제 작업에 더 좋은 성능
- Object: 정적 데이터나 적은 수의 키-값 쌍에 적합
- 기본 키
- Map: 기본적으로 제공되는 키가 없음
- Object:
toString
,constructor
등의 기본 프로토타입 속성이 존재
기본적으로 제공되는 키가 없다는게 무슨말인가?
Object의 경우, 모든 객체는 기본적으로 Object.prototype
으로부터 상속받은 여러 메서드와 속성들을 가지고 있습니다. 예를 들자면:
prototype_test.js
// Object 테스트
const obj = {};
console.log('Object의 기본 키들:');
console.log('toString' in obj); // true
console.log('constructor' in obj); // true
console.log('hasOwnProperty' in obj); // true
console.log('valueOf' in obj); // true
// Map 테스트
const map = new Map();
console.log('\nMap의 기본 키들:');
console.log('toString' in map); // false
console.log('constructor' in map); // false
console.log('hasOwnProperty' in map); // false
console.log('valueOf' in map); // false
// Map의 실제 메서드들
console.log('\nMap의 실제 메서드들:');
console.log(map.set); // function
console.log(map.get); // function
console.log(map.has); // function
console.log(map.delete); // function
- Object의 경우:
toString
,constructor
,hasOwnProperty
,valueOf
등의 기본 메서드들이 프로토타입 체인을 통해 상속됨- 이러한 메서드들은 객체의 키로 사용될 수 있음
- 예:
obj.toString = "something"
과 같이 기본 메서드를 덮어쓸 수 있음
- Map의 경우:
- 기본적으로 제공되는 키가 없음
set
,get
,has
,delete
등의 메서드는 Map 객체의 메서드일 뿐, 키로 사용될 수 없음- Map의 키는 오직 사용자가 명시적으로 추가한 것만 존재
위 특성으로 아래와 같은 이점을 얻습니다.
- Object를 사용할 때는 기본 메서드 이름과 충돌할 수 있음
- Map을 사용하면 이러한 충돌 위험이 없음
- Map은 순수하게 사용자가 추가한 키-값 쌍만 관리
const obj = {}
obj.toString = '이것은 문자열' // 기본 메서드를 덮어씀
console.log(obj.toString()) // 에러 발생!
const map = new Map()
map.set('toString', '이것은 문자열') // 안전하게 키로 사용 가능
console.log(map.get('toString')) // "이것은 문자열" 출력
이러한 특성 때문에 Map은 더 예측 가능하고 안전한 키-값 저장소로 사용될 수 있습니다.
Map의 성능 확인
맵은 순회 시, 성능을 발휘합니다.
- Map은 해시 테이블을 사용하여 O(1) 시간에 접근
- Object는 프로토타입 체인을 검색해야 하므로 추가 오버헤드 발생
- Map은 키-값 쌍을 더 효율적으로 저장
- 검색 성능
- Map: O(1) 시간 복잡도로 검색 가능
- Object: 대부분의 경우 O(1)이지만, 많은 키가 있을 경우 해시 충돌로 인해 성능이 저하될 수 있음
- 메모리 사용
- Map: 더 많은 메모리를 사용 (추가적인 메타데이터 저장)
- Object: 상대적으로 적은 메모리 사용
- 삽입/삭제 성능
- Map: 삽입/삭제 작업이 매우 빠름 (O(1))
- Object: 삽입/삭제 시 프로토타입 체인 검색이 필요해 상대적으로 느림
실제 성능 테스트
performance_test.js
const performance = require('perf_hooks').performance;
// 테스트 데이터 생성
const testSize = 1000000;
const testData = Array.from({ length: testSize }, (_, i) => `key${i}`);
// Map 테스트
console.log('Map 성능 테스트:');
const mapStart = performance.now();
const map = new Map();
for (let i = 0; i < testSize; i++) {
map.set(testData[i], i);
}
const mapInsertTime = performance.now() - mapStart;
const mapSearchStart = performance.now();
for (let i = 0; i < testSize; i++) {
map.has(testData[i]);
}
const mapSearchTime = performance.now() - mapSearchStart;
// Object 테스트
console.log('Object 성능 테스트:');
const objStart = performance.now();
const obj = {};
for (let i = 0; i < testSize; i++) {
obj[testData[i]] = i;
}
const objInsertTime = performance.now() - objStart;
const objSearchStart = performance.now();
for (let i = 0; i < testSize; i++) {
testData[i] in obj;
}
const objSearchTime = performance.now() - objSearchStart;
// 결과 출력
console.log(`Map 삽입 시간: ${mapInsertTime.toFixed(2)}ms`);
console.log(`Map 검색 시간: ${mapSearchTime.toFixed(2)}ms`);
console.log(`Object 삽입 시간: ${objInsertTime.toFixed(2)}ms`);
console.log(`Object 검색 시간: ${objSearchTime.toFixed(2)}ms`);
테스트 결과를 분석
- 삽입 성능
- Map: 500.86ms
- Object: 728.35ms
- Map이 약 1.5배 더 빠름
- 검색 성능
- Map: 11.15ms
- Object: 77.84ms
- Map이 약 7배 더 빠름
사용 시 고려사항
- 작은 데이터셋(수천 개 이하)의 경우: Object 사용이 메모리 효율적
- 대규모 데이터셋이나 빈번한 삽입/삭제: Map 사용이 성능상 유리
- 키가 문자열만 필요한 경우: Object가 더 간단하고 직관적
현재 코드처럼 문자열 검색이 주 목적이라면, Map을 사용하는 것이 성능상 더 유리합니다.