github
javascript

Javascript Object vs Map 당신의 선택은

2025년 6월 24일3 min read

들어가기전

Javascript를 배우거나 사용하다보면 Object 외에 Map이라는 것을 접하게 됩니다.

"Object도 key-value로 저장하는데 Map은 뭐가 다르길래 사용하는거지?"

이런 궁금증을 가져보신 적 있나요?
Map은 ES6에서 도입된 강력한 컬렉션으로, 단순히 Object의 대안이 아닌 고유의 특성과 장점을 가진 자료구조입니다.

Map이 도대체 뭐길래

Map 객체는 key-value 형태로 저장하는 컬렉션으로, 키의 타입에 제한이 없고 삽입 순서를 유지하는 해시맵 자료구조입니다.

탄생배경

Map 이전에는 Object밖에 없어 이를 활용했어야 했습니다. 하지만 이 때 여러 문제들을 겪게 됩니다.

  • 키 타입 제한
  • 프로토타입 오염
  • 크기 확인 비효율성

위의 문제들을 해결하기 위해서 Map이 등장하게 됩니다.

Map 메서드

기본적인 메서드입니다
메서드를 통해 객체보다 동작과 의도를 명확하게 보여줍니다.

const obj = new Map(); //Map 생성

// set 맵에 삽입
obj.set('name','홍길동')
obj.set('age',20)
obj.set('phone','010-1234-5678')

// get 맵 조회
obj.get('age')
// has 존재여부 조회 boolean값 반환
obj.has('age')

// delete 삭제
obj.delete('age')

// keys 삽입 순서에 따라 key를 포함한 반복자 반환
obj.keys().map...

// values 삽입 순서에 따라 값을 포함한 반복자 반환
obj.values().map...
// entries, forEach....

// clear 맵 초기화
obj.clear()

Object와 차이

  1. 프로토타입 키가 없는 깨끗한 상태로 시작합니다.
const obj = {};
const map = new Map();

// Object는 이미 프로토타입 프로퍼티들이 존재
console.log('constructor' in obj);  // true (프로토타입에서 상속)
console.log('toString' in obj);     // true
console.log(Object.keys(obj));      // []

// Map은 키-값 저장소가 완전히 비어있음
console.log(map.has('constructor')); // false
console.log(map.has('toString'));    // false
console.log(map.size);               // 0
	```
2. 크기 정보를 내장하여 즉시 사이즈를 가져올 수 있습니다.
3. key값은 모든 값을 사용할 수 있습니다.
```js
const map = new Map()

map.set(()=>{},'function')
map.set({id:1},'object')
map.set(1,'number')
map.set('string','string')
map.set(false,'boolean')
...
  1. 삽입 순서를 보장합니다.
  2. iterable하기 때문에 직접 반복할 수 있습니다.
const map = new Map();
...

map.forEach(...)
map.entries(...)
  1. 빈번한 추가 및 제거와 관련된 상황에서는 Object보다 성능이 우세합니다
  2. 직렬화에 대한 기본 지원이 없습니다.
    JSON.stringify에 인자로 replacer를 활용하여 지원하게 만들 수 있습니다

어떤 것을 써야할까

Map을 사용해야하는 곳

  • 동적인 키값이 많은 곳
  • 빈번한 추가 및 삭제가 있는 곳
  • 키의 순서가 중요한 곳
  • 프로토타입 오염을 피하고 싶은 곳

Object를 사용해야하는 곳

  • 미리 정해진 정적 구조(설정값)
  • JSON 호환성이 필요한 곳

WeakMap은 무엇이 다를까

WeakMap은 객체만을 키값으로 사용하며, 약한 참조를 통해서 메모리 누수를 방지하는 Map입니다.

왜 WeakMap일까
특정 key에 대한 값이 있는지는 메서드를 통해서 확인되지만, 키로 보유한 객체들을 열거하는 기능은 제공되지 않습니다. 프로그램 내 객체에 대한 참조가 WeakMap을 제외하고 존재하지 않다면 해당 객체는 가비지 컬렉션에 등록됩니다.

ToastUI에서 발췌한 글에 좋은 예시가 있어 가져왔습니다.

class EventEmitter {
  constructor() {
    this.targets = new WeakMap();
  }

  on(targetObject, handlers) {
    if (!this.targets.has(targetObject)) {
      this.targets.set(targetObject, {});
    }

    const targetHandlers = this.targets.get(targetObject);

    Object.keys(handlers).forEach((handlerName) => {
      targetHandlers[handlerName] = targetHandlers[handlerName] || [];
      targetHandlers[handlerName].push(handlers[handlerName].bind(targetObject));
    });
  }

  fire(targetObject, handlerName, args) {
    const targetHandlers = this.targets.get(targetObject);

    if (targetHandlers && targetHandlers[handlerName]) {
      targetHandlers[handlerName].forEach((handler) => handler(args));
    }
  }
}
const emitter = new EventEmitter();

function start() {
  const user = {
    name: 'John',
  };

  const handlers = {
    sayHello: function () {
      console.log(`Hello, my name is ${this.name}`);
    },
    sayGoodBye: function () {
      console.log(`Good bye, my name is ${this.name}`);
    },
  };

  emitter.on(user, handlers);

  const timer = setInterval(() => {
    emitter.fire(user, 'sayHello');
    emitter.fire(user, 'sayGoodBye');
  }, 1000);

  setTimeout(function () {
    clearInterval(timer);
  }, 5001);
}

위에 있는 코드는 ToastUI에서 작성된 코드로 CustomEvent를 위한 코드입니다. 지금은 WeakMap으로 작성되어 Map으로 바꾸어 어떤차이가 있는지 확인할 수 있습니다.

우선 코드를 보면 EventEmitter 인스턴스를 생성하고 start 함수를 호출하면
필요한 객체들을 생성하여 EventEmitter에 등록하고 setInterval을 통해 반복적으로 이벤트를 실행합니다.

함수가 종료된 이후에는 user의 객체를 참조하고 있는 것은 없기 때문에 WeakMap에서는 가비지 콜렉터에 등록시켜 수거를 하게 됩니다

Map의 경우 남아있는 객체

Reference

profile
한동룡