`Map` 객체

이 글에서는 Map 객체에 대해 설명합니다.

기본 동작부터 실제로 유용하게 사용할 수 있는 실전 예제까지 단계별로 설명하겠습니다.

YouTube Video

Map 객체

Map은 키와 값의 쌍을 저장하는 컬렉션입니다. 객체와 유사하지만, 객체, 함수, 원시값 등 어떠한 타입도 키로 사용할 수 있고, 삽입 순서가 유지된다는 점에서 다릅니다.

Map의 기본

먼저, Map을 만드는 방법과 기본 동작을 살펴봅시다.

아래 코드는 빈 맵을 만들고 키를 추가한 후 값을 가져오는 최소 예제입니다.

1// Create an empty Map and add key-value pairs
2const m = new Map();
3m.set('a', 1);
4m.set('b', 2);
5
6console.log(m.get('a')); // 1
7console.log(m.size);     // 2
  • 이 코드에서는 set으로 요소를 추가하고, get으로 값을 가져오며, size로 요소의 개수를 확인할 수 있습니다.
  • Map은 삽입 순서를 보장하므로, 순서가 중요한 처리에 적합합니다.

set, get, has, delete의 동작

여기에서 기본적인 읽기, 쓰기, 존재 확인, 삭제 동작의 예시를 보여줍니다.

다음 코드를 통해 각 메서드의 반환값과 효과를 확인할 수 있습니다.

 1// Demonstrate set, get, has, and delete
 2const m2 = new Map();
 3m2.set('x', 10);
 4console.log(m2.has('x'));  // true
 5console.log(m2.get('y'));  // undefined
 6
 7m2.delete('x');
 8console.log(m2.has('x'));  // false
 9
10// set returns the map itself, allowing method chaining
11m2.set('a', 1).set('b', 2);
12console.log(m2);  // Map { 'a' => 1, 'b' => 2 }
  • 키가 존재하지 않으면 getundefined를 반환합니다. has는 키의 존재 여부를 확인하고, delete는 키를 제거합니다.
  • 또한, set이 Map 자신을 반환하므로 메서드 체이닝이 가능합니다.

모든 타입을 키로 사용할 수 있음(객체를 키로 사용하기)

Map의 주요 이점 중 하나는 객체를 직접 키로 사용할 수 있다는 것입니다.

아래 예시는 객체를 키로 하여 Map에서 값을 연결하는 방법을 보여줍니다.

 1// Use objects as keys in a Map
 2const keyObj = { id: 1 };
 3const keyFunc = () => {};
 4const objMap = new Map();
 5
 6// Another object with the same content but a different reference
 7const anotherKeyObj = { id: 1 };
 8
 9objMap.set(keyObj, 'objectValue');
10objMap.set(keyFunc, 'functionValue');
11objMap.set(anotherKeyObj, 'anotherValue');
12
13console.log(objMap.get(keyObj));         // 'objectValue'
14console.log(objMap.get(keyFunc));        // 'functionValue'
15console.log(objMap.get(anotherKeyObj));  // 'anotherValue'
  • 객체를 키로 사용할 때는 참조가 동일해야 한다는 점이 중요합니다. 내용이 같더라도 참조가 다르면 서로 다른 키로 간주되어 동일하지 않습니다.

반복(루프)

Map은 삽입 순서를 유지하므로, 반복 처리가 자주 사용됩니다.

아래에서는 for...of, forEach, keys(), values(), entries() 메서드의 사용 예를 소개합니다.

 1// Iterating a Map with for...of and forEach
 2const iterMap = new Map([['a', 1], ['b', 2], ['c', 3]]);
 3
 4// for...of over entries (default)
 5for (const [key, value] of iterMap) {
 6  console.log(key, value);
 7}
 8
 9// forEach callback
10iterMap.forEach((value, key) => {
11  console.log(key, value);
12});
13
14// keys() and values()
15console.log([...iterMap.keys()]);   // ['a','b','c']
16console.log([...iterMap.values()]); // [1,2,3]
  • entries()[key, value] 쌍의 이터러블을 반환하며 스프레드 문법으로 배열로 만들 수 있습니다. forEach의 콜백은 value, key 순서로 인자를 받는다는 점에 주의하세요.

MapObject 변환하기

기존 객체를 Map으로 변환하거나, Map을 일반 객체나 배열로 변환할 수 있습니다.

 1// Convert between Map and Object / Array
 2const obj = { a: 1, b: 2 };
 3const mapFromObj = new Map(Object.entries(obj)); // Object -> Map
 4console.log(mapFromObj.get('a')); // 1
 5
 6const objFromMap = Object.fromEntries(mapFromObj); // Map -> Object
 7console.log(objFromMap); // { a: 1, b: 2 }
 8
 9const arrayFromMap = [...mapFromObj]; // Map -> Array of [key, value]
10console.log(arrayFromMap); // [['a',1], ['b',2]]
  • Object.entriesObject.fromEntries를 이용하면 변환이 간편합니다. 하지만 객체의 키는 문자열이나 심볼만 사용할 수 있기 때문에, 객체로 다시 변환할 때 문자열이 아닌 키는 유실됩니다.

실전 패턴: 빈도수 세기(카운트 맵)

배열에서 요소의 빈도를 셀 때, Map을 사용하면 과정이 더 간단해집니다.

다음 코드는 배열 안의 문자열 빈도를 Map으로 세고 정렬하는 예제입니다.

1// Count frequencies with Map
2const arr = ['apple','banana','apple','orange','banana','apple'];
3const freq = new Map();
4
5for (const item of arr) {
6  freq.set(item, (freq.get(item) || 0) + 1);
7}
8
9console.log([...freq.entries()]); // [['apple',3], ['banana',2], ['orange',1]]
  • Map을 사용하면 존재 여부 확인과 값 갱신이 쉽습니다. 객체로도 할 수 있지만, 임의의 키를 다룰 때는 Map이 더 직관적입니다.

MapJSON.stringify(직렬화) 주의점

JSON.stringifyMap을 직접 직렬화할 수 없습니다. Map을 저장해야 한다면 먼저 변환해야 합니다.

아래 예시는 Map을 배열로 변환한 뒤 JSON으로 만들고, 그 후 복원하는 방법을 보여줍니다.

1// Serialize and deserialize a Map
2const m3 = new Map([['x', 1], ['y', 2]]);
3const json = JSON.stringify([...m3]); // convert to array first
4console.log(json); // '[["x",1],["y",2]]'
5
6const restored = new Map(JSON.parse(json));
7console.log(restored.get('x')); // 1
  • 저장하거나 전송하려는 Map은 직렬화 전에 배열로 변환해야 합니다. 복원할 때는 JSON.parse로 배열로 만든 뒤, 다시 Map으로 변환합니다.

WeakMap 소개와 사용법

WeakMap은 키가 약하게 참조되어(가비지 컬렉션 가능) 다르다는 점이 있습니다.

키 객체가 가비지 컬렉션될 때 자동으로 해제되어, 객체를 키로 캐시나 메타데이터를 저장할 때 유용합니다.

1// WeakMap for metadata tied to object lifecycle
2const wm = new WeakMap();
3let obj = {};
4wm.set(obj, { meta: 'info' });
5console.log(wm.get(obj)); // { meta: 'info' }
6
7obj = null; // now the object can be GC'd and its entry removed from WeakMap
  • WeakMap은 반복하거나 크기를 확인할 수 없지만, 메모리 누수를 방지하는 데 유용합니다.

요약

Map은 객체와 달리 어떤 타입이든 키로 쓸 수 있고, 삽입 순서를 보장하는 편리한 컬렉션입니다. 기초 동작부터 고급 사용법까지 익히면 데이터를 더 유연하고 직관적으로 관리할 수 있습니다. 상황에 따라 ObjectMap을 적절히 사용하면 코드 명확성과 가독성을 크게 향상시킬 수 있습니다.

위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.

YouTube Video