커리어리에 올라온 포스트들 가운데 관심이 갔던 주제에 대해 다루어보았다.
사용자의 액션에 대한 결과를 띄워주기 위한 Result 컴포넌트를 구현했다. 이때 Result의 status 상태에 따라 각기 다른 아이콘을 띄워주기 위해 특정 status(key)를 통한 아이콘 정보(value)를 얻고자 하는 상황이 있었다. 이러한 경우가 개발하다보면 정말 많았는데 나는 항상 고민도 하지 않고 Object를 사용해 데이터를 검색하곤 했던 것 같다.
const ResultIconByStatus: Record<Status, ResultIconInfo> = {
info: { name: 'info-rounded', color: '#005FFC' },
success: { name: 'check-circle-rounded', color: '#00C021' },
warning: { name: 'warning', color: '#FFA825' },
error: { name: 'close-circle', color: '#FF464A' },
};
위와 같은 케이스를 정리하면 다음과 같겠다. key를 통해 데이터를 검색해 value 값을 얻는 상황
.
이러한 자료구조 중 하나로 제목에서 정의된 해시 맵이 있다. 해시 맵은 <key, value> 쌍으로 데이터를 관리하며 많은 양의 데이터 가운데 key를 통한 검색이 가능한 구조다.
모던 자바스크립트 Deep Dive의 정의를 인용하면,
자바스크립트 Object는 프로퍼티와 메서드로 구성된 집합체로, 이처럼 객체는 상태와 동작을 하나의 단위로 구조화할 수 있어 유용하다로 정의된다.
Object는 위 설명대로 상태와 동작을 하나로 저장할 수 있는 데이터 타입이다. 위의 코드는 여러 상태를 갖고 있지만, 상태들이 하나의 단위로 이용되지는 않아 보인다. 이러한 경우 Object 대신 Map
을 이용해볼 수 있다.
const map = {};
// key-value pair 넣기
map['key1'] = '값1';
map['key2'] = '값2';
map['key3'] = '값3';
// 특정 key를 가지고 있는지 확인하기
if (map.hasOwnProperty('key1')) {
console.log('Map이 key1을 포함하고 있음.');
}
// 특정 key의 value를 찾기
console.log(map['key1']);
자바스크립트에서 Map을 Object 대신 사용하면 여러 가지 장점이 있다.
Object는 key로 Symbol과 String 타입만 이용할 수 있다. Map은 key로 어떤 타입이든 사용 가능하다.
const map = new Map();
const myFunction = () => console.log('I am a useful function.');
const myNumber = 666;
const myObject = {
name: 'plainObjectValue',
otherKey: 'otherValue'
};
map.set(myFunction, 'function as a key');
map.set(myNumber, 'number as a key');
map.set(myObject, 'object as a key');
console.log(map.get(myFunction)); // function as a key
console.log(map.get(myNumber)); // number as a key
console.log(map.get(myObject)); // object as a key
Map은 크기를 O(1)의 시간 복잡도로 구할 수 있으나 Object는 O(n)의 시간이 소요된다. 또한 크기를 구하는 방식도 간단하다.
const map = new Map();
map.set('someKey1', 1);
map.set('someKey2', 1);
...
map.set('someKey100', 1);
console.log(map.size) // 100, Runtime: O(1)
const plainObjMap = {};
plainObjMap['someKey1'] = 1;
plainObjMap['someKey2'] = 1;
...
plainObjMap['someKey100'] = 1;
console.log(Object.keys(plainObjMap).length) // 100, Runtime: O(n)
Map은 설계단계부터 데이터의 추가와 제거에 최적화 되어 있기 때문에 성능에 있어서 매우 유리하다. Object는 key-value 쌍의 빈번한 추가 및 제거에 최적화되어 있지 않다.
원문에 따르면 맥북프로에서 천만개의 데이터 셋을 가지고 테스트 했을 때 Object는 1.6초의 처리시간이 필요했고 Map은 1ms 이하의 처리시간을 보였다고 한다.
Object는 key들을 먼저 찾아낸 다음 그것들을 토대로 순회한다. 하지만 Map은 그 자체가 iterable 하기 때문에 직접 반복할 수 있다.
const map = new Map();
map.set('someKey1', 1);
map.set('someKey2', 2);
map.set('someKey3', 3);
for (let [key, value] of map) {
console.log(`${key} = ${value}`);
}
// someKey1 = 1
// someKey2 = 2
// someKey3 = 3
const plainObjMap = {};
plainObjMap['someKey1'] = 1;
plainObjMap['someKey2'] = 2;
plainObjMap['someKey3'] = 3;
for (let key of Object.keys(plainObjMap)) {
const value = plainObjMap[key];
console.log(`${key} = ${value}`);
}
// someKey1 = 1
// someKey2 = 2
// someKey3 = 3
ECMAScript 2015 이전 버전에서는 Object에서 키의 순서를 보장하지 않았다. 현재는 지원된다.
Map은 추가한 순서에 따른 순번을 보장한다.
Object에는 미리 정의된 prototype이 있기 때문에 key가 충돌할 위험이 있다(toString, constructor, valueOf 등). 하지만 Map을 사용할 때는 그런 걱정을 할 필요가 없다. Map은 기본적으로 키를 포함하지 않는다. 여기에는 명시적으로 입력된 내용만 포함된다.
const map = new Map();
map.set('키1', 1);
map.set('키2', 2);
map.set('toString', 3); // Map에서는 문제 없습니다.
const plainObjMap = {};
plainObjMap['키1'] = 1;
plainObjMap['키2'] = 2;
plainObjMap['toString'] = 3; // toString은 이미 선점되어 있습니다. toString()을 사용할 수 없게됩니다.
그동안 JS로 많은 프로젝트를 진행해보았지만 한 번도 Map을 사용해본 적이 없는 것 같다(😂). 그래서 커리어리에서 관련 포스트를 봤을 때 뜨끔하면서도 관심이 갔다. Object와 Map의 정의, 쓰임은 자바스크립트 면접 대비를 하면서 그 개념을 확실히 정리했었는데, 적용은 하지 못하고 있었다는 사실을 발견했다. 포스트의 내용은 정말 유익했던 것 같다. Map과 Object를 비교해볼 수 있어서 좋았고, 다른 프로젝트를 하면서 바로 적용해볼 수 있겠다는 기대가 들었다. 그리고 데이터 검색에서 너무나 당연하게 Object를 썼던 나를 반성하며 앞으로는 필요에 의해, 근거를 갖고 Object와 Map 중에 선택해서 쓰는 것이 필요할 것 같다.