JavaScript, jQuery

Map & Set 완전 정복 – 언제, 왜, 어떻게 써야 할까요?

jonbeo 2025. 9. 1. 10:46
반응형

 

 

안녕하세요 😊
오늘은 ES6에서 도입된 컬렉션 객체 **Map**과 **Set**을 정리해보겠습니다.
Map키-값 저장소, Set중복 없는 값 집합으로, 기존 ObjectArray의 한계를 보완합니다.
실무 예제와 함께 “언제 어떤 걸 써야 하는지”까지 알아보겠습니다.


1) Map 한눈에 보기 – “모든 타입을 키로 쓰는 해시 맵”

특징

  • 키에 어떤 타입이든 사용 가능(객체, 함수, NaN 포함)
  • 삽입 순서 보장
  • 크기: map.size
  • 반복: for...of, map.forEach()
 
// 생성 & 기본 메서드
const m = new Map();
m.set('name', 'Kim');
m.set({ id: 1 }, 'object-key');
m.set(NaN, 'not-a-number');

console.log(m.get('name'));  // 'Kim'
console.log(m.has(NaN));     // true
m.delete('name');
console.log(m.size);         // 2

 

Object와 무엇이 다른가요?

  • Object는 문자열/심볼만 키로 안정적 사용 → 객체를 키로 쓰기 번거로움
  • 키 개수는 직접 세야 함 → Mapsize 제공
  • 빈번한 추가/삭제·검색에선 Map일반적으로 더 직관적이고 성능 이점

자주 쓰는 패턴

  • 빈도 계산(워드 카운트), 캐시(메모이제이션), 그래프 인접 리스트 등
 
// 빈도 계산
const words = ['a','b','a','c','b','a'];
const freq = new Map();
for (const w of words) {
  freq.set(w, (freq.get(w) || 0) + 1);
}
// [['a',3], ['b',2], ['c',1]]

2) Set 한눈에 보기 – “중복 없는 값들의 집합”

특징

  • 값의 중복 자동 제거
  • 삽입 순서 보장
  • 크기: set.size
  • 반복: for...of, set.forEach()
 
const s = new Set([1,2,2,3]);
console.log([...s]); // [1,2,3]

s.add(4);
console.log(s.has(2)); // true
s.delete(1);
s.clear();

 

Array와 무엇이 다른가요?

  • 배열에서 중복 제거하려면 filter/indexOf가 필요 → Set이 더 간단·빠름

실무 패턴

  • 중복 제거
const unique = [...new Set(['a','a','b','c','b'])]; // ['a','b','c']

 

  • 교집합 / 차집합 / 합집합
 
const A = new Set([1,2,3]);
const B = new Set([2,3,4]);

const union = new Set([...A, ...B]);                         // {1,2,3,4}
const intersection = new Set([...A].filter(x => B.has(x)));   // {2,3}
const difference = new Set([...A].filter(x => !B.has(x)));    // {1}

3) 순회와 변환 – Map/Set ↔ Array/Object

// Map ↔ Array
const map = new Map([['name','Lee'], ['age',28]]);
const arr = [...map]; // [['name','Lee'], ['age',28]]

// Set ↔ Array
const set = new Set([1,2,3]);
const arr2 = [...set]; // [1,2,3]

// Object → Map
const obj = { a: 1, b: 2 };
const m2 = new Map(Object.entries(obj));

// Map → Object (키가 문자열일 때만 안전)
const obj2 = Object.fromEntries(m2);

4) 성능과 복잡도 감각치

  • Map: 평균적으로 get/set/has/delete 모두 O(1) 기대
  • Set: add/has/delete O(1) 기대
  • Array 중복 체크(includes)는 O(n)Set 선호
  • 크고 빈번한 조회/추가/삭제 로직이면 Map/Set이 유리

5) WeakMap / WeakSet – 메모리 친화적 참조

  • 키/값으로 오직 객체 참조만 허용 (원시값 불가)
  • 가비지 컬렉션 대상에서 자동 해제(참조가 끊기면 메모리 회수)
  • 반복·size 없음(관찰 불가 구조)
 
const wm = new WeakMap();
let objKey = { id: 1 };
wm.set(objKey, 'private-data');
// objKey = null 로 참조 끊기면, 해당 엔트리 GC 대상

 

✅ 사용처: 컴포넌트 인스턴스 → 메타데이터, DOM 노드 → 상태 맵핑


6) 실무 시나리오 모음

A. 중복 없는 태그 목록 만들기 (Set)

const posts = [
  { tags: ['js','web'] },
  { tags: ['web','react'] },
  { tags: ['css','js'] }
];
const tags = new Set(posts.flatMap(p => p.tags));
console.log([...tags]); // ['js','web','react','css']

B. 객체 키로 상태 저장하기 (Map)

 
const stateByUser = new Map();
const user1 = { id: 1 }, user2 = { id: 2 };

stateByUser.set(user1, { online: true });
stateByUser.set(user2, { online: false });

console.log(stateByUser.get(user1).online); // true

C. LRU 캐시 느낌의 최근 본 항목(중복 제거 + 최신 순서) (Set)

 
const recent = new Set();
function see(item) {
  if (recent.has(item)) recent.delete(item); // 위치 갱신
  recent.add(item);
  if (recent.size > 5) recent.delete(recent.values().next().value); // 가장 오래된 항목 제거
}
['a','b','c','a','d','e','f'].forEach(see);
console.log([...recent]); // 최근 본 5개 유지

7) 자주 하는 실수 & 주의점

  • MapJSON 직렬화가 바로 안 됨Object.fromEntries(map) 등으로 변환 후 저장
  • Set은 값의 동일성 비교가 === 기준 (객체 레퍼런스가 다르면 다른 값)
  • Object에 비해 Map직접 프로퍼티 접근(obj.key) 불가 → 반드시 get/set 사용
  • 열거 순서 필요 & 키가 비원시값이면 Map 우선,
    단순 키-값(문자열 키) + 직렬화가 중요하면 Object 고려

✅ 마무리

  • Map: “모든 타입 키 + 빠른 조회/수정/삭제 + 순서 보장”이 필요할 때
  • Set: “중복 제거, 집합 연산, 빠른 포함 검사”가 필요할 때
    이 둘을 익숙하게 쓰시면, 로직이 깔끔해지고 성능도 안정적으로 개선됩니다.
    실무에서 배열·객체만 고집하지 말고, 상황에 맞춰 Map/Set을 선택해 보세요! 😊
반응형