햄스터 갬성 블로그

[JS] Map

어떤 프로그래밍 언어로 개발하던 Map은 유용하게 사용된다. 옆 동네 Python에서 Map을 쓰는 빈도를 봐도 알 수 있다. 하지만 안타깝게도 Map은 ES6 전까지 자바스크립트에 존재하지 않았다. 대신 Object에 몇 가지 꼼수를 부리며 그 빈자리를 달랠 뿐이었다. 보통 Object에 연결된 prototype을 제거하기 위해 Object.create(null)을 이용한다. 만약 평범한 Object를 사용했다면 주렁주렁 달려 있는 property 때문에 Map에 key 삽입시 충돌이 일어날 수도 있다. 이번 포스트에서는 ES6에 새롭게 추가된 데이터 구조 Map에 대해 알아보자.

Google Map 할 때 그 Map? #

Map은 Object와 비슷하게 key와 value를 쌍으로 저장한다. 이때, key는 중복되지 않는다. 만약 이미 저장된 key가 또 들어오면 기존 value를 새 값으로 덮어쓴다. set(key, value) 함수로 key와 value를 저장하고 get(key)로 그 key와 연결된 value를 반환한다. 그리고 new Map()으로 Map을 생성한다. new를 쓰는 거로 보아 Map의 타입이 Object임을 알 수 있다. 마지막으로 삭제는 delete(key)로 하며 clear()로 모든 key, value 쌍을 지운다. 아래 코드에 기본적인 Map 사용법이 나와 있다.

var myMap = new Map();

typeof myMap // "object"

myMap.set('name', 'Tom');
myMap.set('age', 15);
myMap.set('name', 'Sam');

myMap.get('name'); // "Sam"
myMap.get('age'); // 15

myMap.delete('name') // true
myMap.get('name') // undefined

myMap.clear()
myMap.get('age') // undefined

Map과 Object #

여기까지 보면 Map은 Object와 상당히 유사하다. 그렇다면 왜 Map이 필요한 걸까? Map이 Object보다 나은 몇 가지 이유를 살펴보자.

Object의 key는 반드시 String이거나 Symbol이다. 앞선 포스트에서 봤듯이 ES6에서 새로 추가된 타입인 Symbol은 Object의 key로 사용될 수 있다. 반면에 Map의 key는 모든 타입을 허용한다. Number, Boolean은 물론이고 Object까지 가능하다. 즉, Object에 포함되는 function이나 array도 key로 사용할 수 있다.

var myMap = new Map();

var keyString = 'string key';
var keyObject = { key: 'object key' };
var keyFunction = function() { return 'function key' };
var keySymbol = Symbol('symbol key');

myMap.set(keyString, 'I am a string key');
myMap.set(keyObject, 'I am a object key');
myMap.set(keyFunction , 'I am a function key');
myMap.set(keySymbol, 'I am a symbol key');

myMap.get(keyString) // "I am a string key"
myMap.get(keyObject) // "I am a object key"
myMap.get(keyFunction); // "I am a function key"
myMap.get(keySymbol); // "I am a symbol key"

위 코드처럼 모든 타입이 Map의 key로 사용될 수 있다.

사소하지만 꽤 강력한 차이점이 하나 더 있다. Object는 크기를 구하는 built-in(이미 구현된) method나 property가 없다. Object.keys(object).length가 그나마 쉬운 방법이다. 하지만 time complexity가 linear하므로 Object가 커질수록 더 많은 시간이 소요된다. 반면에 Map은 size property로 크기를 단번에 구할 수 있다. 이 경우 size property가 수시로 갱신되므로 Map의 크기가 커져도 걸리는 시간에 영향을 주지 않는다.

myMap.size // 4

Map 가지고 지지고 볶기 #

Map에 저장된 key와 value를 쭉 늘여놓고 싶으면 values()keys() 함수를 사용하면 된다. 단, 이 두 함수는 iterator를 반환한다. 따라서 추가적인 작업이 들어가야 예쁘게 표현될 수 있다. 지금 단계에서 iterator를 몰라도 괜찮다. 다음 포스트에서 다룰 예정이다. 이제 아래 코드를 보자.

var myMap = new Map();
myMap.set('a', 1);
myMap.set('b', 2);
myMap.set('c', 3);

myMap.values(); // MapIterator {1, 2, 3}

[ ...myMap.values() ]; // [1, 2, 3]
Array.from ( myMap.values() ); // [1, 2, 3]

values() 함수를 사용하면 Map에 저장된 모든 value가 iterator로 반환된다. iterator를 푸는 가장 원초적인 방법은 for문을 쓰는 것이다. 하지만 훨씬 더 손쉬운 방법이 ES6에 존재한다. Spread operator ... 와 새로운 Array API from()을 쓰면 iterator를 array로 변환시킬 수 있다. 하지만 이 부분은 이번 포스트의 범위를 넘어가므로 다음에 다루겠다. 지금은 values()가 value의 iterator를 반환하고 이를 푸는 새로운 방법이 ES6에 등장했다는 정도만 알고 있자. keys()는 key의 iterator를 반환하며 나머지는 values()와 비슷하게 작용한다.

Reference
Map - JavaScript | MDN
ES6 In Depth: Collections
You Don't Know JS: ES6 & Beyond