Post

[자바스크립트] Map에 관하여 | LeetCode 594번 문제를 풀다가...

[자바스크립트] Map에 관하여 | LeetCode 594번 문제를 풀다가...

LeetCode 594번 문제를 풀던 중, Map 자료구조에서 예상과 다른 동작을 마주했다.

무의식적으로 Map을 Object처럼 다루며 map[key] = value 형태로 값을 저장했지만, 에러는 발생하지 않았다.

map.size, get, has,for...of 순회에서도 해당 값이 전혀 잡히지 않았다. 그럼에도 런타임 환경에서는 아무런 에러가 발생하지 않았다.

왜 잘못된 코드인데도 에러가 발생하지 않았을까?

이 글은 그 질문에서 출발해 Map과 Object의 기본 사용법을 알고 있다는 전제하에, Map을 Object처럼 사용했을 때 발생하는 JavaScript의 ‘silent failure’와 그에 관련된 개념들을 정리한 기록이다.


문제 상황: 왜 map[key] = value가 에러 없이 동작할까?

1
2
const map = new Map();
map[1] = "A";

이 코드는 에러 없이 실행된다. 하지만 결과는 다음과 같다.

1
2
3
map.size      // 0
map.get(1)    // undefined
map[1]        // "A"

겉보기엔 값이 들어간 것처럼 보이지만, Map이 의도한 방식으로는 아무것도 저장되지 않았다.



당연하지만,

이유 1: Map도 결국 객체(Object)이기 때문이다

JavaScript에서 .(점 표기)와 [](대괄호 표기)는 “자료구조에 값을 넣는다”는 의미가 아니라,
항상 객체의 프로퍼티(property)를 읽고/쓰는 문법이다.

1
2
map.foo = 1;
map["bar"] = 2;

이 문법의 의미는 항상 동일하다.

“map 객체에 foo / bar라는 프로퍼티를 추가한다”

❗️ JavaScript는 동적 언어이기 때문에 객체에 새로운 프로퍼티를 추가하는 행위를 에러로 취급하지 않는다.

Map 역시 객체이므로, map[key] = value와 같은 문법을 특별히 차단하지 않는다.


이유 2: Map의 실제 데이터는 내부 슬롯에 저장된다

Map의 실제 데이터는 객체 프로퍼티가 아니라 ⛔️ 내부 슬롯(internal slot) 에 저장된다.

개념적으로 Map은 다음과 같은 구조를 가진다.

1
2
3
4
5
6
Map 객체
 ├─ [[MapData]]  ← 엔트리 저장소 (internal slot)
 │    ├─ Entry 1: (key, value)
 │    ├─ Entry 2: (key, value)
 │    └─ Entry 3: (key, value)
 └─ 기타 인스턴스 메서드 (get, set, has, size 등 / prototype)
  • [[MapData]]는 JS 코드에서 직접 접근 할 수 없다
  • 오직 set / get / has / delete를 통해서만 조작된다

따라서 다음 두 코드는 완전히 다른 의미를 가진다.

1
2
map.set(k, v); // ⭕ Map의 엔트리 저장소에 저장
map[k] = v;    // ❌ Map 객체의 겉에 프로퍼티 추가

map[key] = value는 엔트리를 만든 게 아니라, Map 객체의 겉에 프로퍼티를 하나 붙인 것에 불과하다.


그래서 왜 에러가 아니라 ‘조용히 실패’할까?

1
2
const obj = {};
obj.foo = 1; // 정상 동작

JavaScript는 객체에 존재하지 않는 프로퍼티를 추가하는 행위를 에러로 취급하지 않는다.

Map 역시 객체이기 때문에, 잘못된 접근 방식이라 하더라도 런타임 에러는 발생하지 않는다.

그 결과, 코드는 실패했지만 JavaScript는 이를 실패로 간주하지 않는다.

이것이 Map을 Object처럼 사용할 때 가장 위험한 지점이다.


Map의 핵심 개념: 엔트리(entry)

Map에서 말하는 엔트리(entry) 는 다음을 의미한다.

(key, value) 한 쌍 = Map의 엔트리

1
2
map.set(1, "A");
map.set("1", "B");

이 Map에는 엔트리 2개가 존재한다.

  • Entry 1: key 1, value “A”
  • Entry 2: key “1”, value “B”

중요한 점은 다음과 같다.

  • map.size는 엔트리 개수
  • 엔트리는 Map 내부 저장 단위
  • 프로퍼티와는 완전히 다른 개념

Map을 Object처럼 쓰면 생기는 문제

Map을 Object처럼 사용하면, Map이 제공하는 모든 장점을 잃게 된다.

  • map.size 증가 ❌
  • map.get() / map.has() / map.delete() 사용 ❌
  • for (const [k, v] of map) 순회 ❌
  • map.keys() / map.values() ❌

즉,

Map을 사용하는 의미 자체가 사라진다

Object와 Map의 차이

Object와 Map은 모두 key-value 구조를 가지지만, 설계 목적은 완전히 다르다.

구분ObjectMap
설계 목적데이터 모델컬렉션
기본 단위PropertyEntry
key 타입string / symbol모든 타입
순서 보장규칙 복잡명확
size

JavaScript에서는 많은 경우,</br> 잘못된 코드가 에러 없이 동작한다.

그래서 더더욱,</br> 자료구조의 ‘사용법’이 아니라 ‘의도된 모델’을 이해한 상태에서 코드를 작성해야 한다.


다음 글에서는, 객체의 프로토타입과 프로퍼티 탐색 과정에 대해 정리해보려고 한다.

This post is licensed under CC BY 4.0 by the author.