🎣

[JS] 맵과 셋(Map & Set)


Map

맵은 키가 있는 데이터를 저장한다. 객체와 비슷하지만, 객체와는 다르게 맵은 키에 다양한 자료형을 사용할 수 있다.

맵은 객체도 키로 사용할 수 있다.

객체는 키를 문자형으로 자동 변환한다.

주요 메서드와 프로퍼티

  • new Map()
  • map.set(key, value): key를 이용해 value를 저장하고 맵 자신을 반환한다..
  • map.get(key): key에 해당하는 값을 반환한다. 존재하지 않으면 undefined를 반환한다.
  • map.has(key)
  • map.delete(key): 해당하는 값을 삭제한다.
  • map.clear(): 맵 안의 모든 요소를 제거한다.
  • map.size: 요소의 개수
1let map = new Map();
2map.set('1', 'string');
3map.set(1, 'number');
4
5console.log(map.get(1));
6// -> number
7console.log(map.get('1'));
8// -> string
  • map.keys(): 각 요소의 키에 대한 iterable을 반환한다.
  • map.values(): 각 요소의 값에 대한 iterable을 반환한다.
  • map.entries(): 요소의 [키, 값]을 한 쌍으로 하는 iterable을 반환한다. for..of 반복문의 기초로 쓰인다.
  • map.forEach((value, key, map) => {})
1let drink = new Map([['beer', 3000], ['soju', 1600]]);
2
3for (const name of drink.keys()) {
4 console.log(name);
5}
6// -> beer
7// -> soju
8for (const cost of drink.values()) {
9 console.log(cost);
10}
11// -> 3000
12// -> 1600

맵은 값이 삽입된 순서대로 순회를 실시한다.

객체를 맵으로, 맵을 객체로

Object.entries

맵에 각 요소가 '키-값' 쌍인 배열이나 iterable을 전달해 초기화할 수 있다.

1let drink = new Map([['beer', 3000], ['soju', 1600]]);

평범한 객체를 맵으로 맨들고 싶다면 Object.entries(obj)를 활용해야 한다. 이 메서드는 객체의 [키, 값]을 요소로 가지는 배열을 반환한다.

1let obj = {
2 beer: 3000,
3 soju: 1600
4};
5
6let map = new Map(Object.entries(obj));
7console.log(map.get('beer'));
8// -> 3000

Object.formEntries

Object.fromEntries를 사용하면 맵을 객체로 바꿀 수 있다. 각 요소가 [키, 값]인 배열을 객체로 바꿔준다.

1let drink = new Map([['beer', 3000], ['soju', 1600]]);
2
3let obj = Object.fromEntries(drink.entries());
4// Or
5let obj = Object.fromEntries(drink);
6
7console.log(obj);
8// -> {beer: 3000, soju: 1600}

Set

셋은 중복 없는 값을 모아놓는 컬렉션이다. 셋에는 키가 없는 값이 저장된다.

셋은 값의 유일무이함을 확인하는데 최적화되어있다.

주요 메서드와 프로퍼티

  • new Set(iterable)
  • set.add(value): 값을 추가하고 셋 자신을 반환한다. 셋 내에 동일한 값이 있다면 set.add(value)를 여러번 호출해도 아무 반응이 없다.
  • set.delete(value): 값을 제거하고, 성공하면 true, 실패하면 false를 반환한다.
  • set.has(value): 셋 내에 값이 존재하면 true, 아니면 false를 반환한다.
  • set.clear()
  • set.size
1let set = new Set();
2
3set.add('beer');
4set.add('soju');
5set.add('beer');
6set.add('beer');
7
8console.log(set.size);
9// -> 2

맵도 셋과 마찬가지로 반복 작업을 위한 메서드가 있다. 모두 iterable을 반환한다.

  • set.keys()
  • set.values()
  • set.entries()
  • set.forEach((value, valueAgain, set) => {})

셋의 forEach의 콜백은 똑같은 값을 두 번 받는다. 원래는 값과 키가 오지만, 셋에는 키가 없기 때문에 값이 두 개 온다. 이는 배열과 맵에서 사용하는 forEach와 동일한 형태를 유지하기 위함이다.

위크맵과 위크셋

위크맵을 사용하면 키로 쓰인 객체가 가비지 컬렉션의 대상이 된다.

자료구조를 구성하는 요소들은 대게 자신이 속한 자료구조가 메모리에 남아있는 동안 도달 가능한 값으로 취급되어 메모리에서 삭제되지 않는다. 아래 예시를 통해 확인할 수 있다.

1let woong = { name: "Woong" };
2
3let array = [woong];
4
5woong = null;
6
7console.log(array[0]);
8// -> {name: "Woong"}

맵과 셋을 사용했을 때도 마찬가지다.

하지만 위크맵/셋은 전혀 다른 양상을 보인다. 위크맵을 사용하면 키로 쓰인 객체가 가비지 컬렉션의 대상이 된다.

1let weakMap = new WeakMap();
2
3let woong = { name: "Woong" };
4
5weakMap.set(woong, "Hello");
6
7woong = null;
8// woong을 나타내는 객체가 메모리에서 지워진다.

woong을 나타내는 객체는 위크맵의 키로만 사용되고 있기 때문에 참조를 덮어쓰면 객체가 위크맵과 메모리에서 자동으로 삭제된다.

맵과 위크맵은 이것 말고도 두 가지 차이점을 가진다.

  • 위크맵의 키에는 객체만 올 수 있다. 원시값은 올 수 없다.
  • 위크맵은 반복 작업과 keys(), values(), entries() 메서드를 지원하지 않는다. 따라서 위크맵에선 키나 값 전체를 얻는 것이 불가능하다.

위크맵이 제공하는 메서드는 get(key), set(key, value), delete(key), has(key)가 끝이다. 전부 단일 요소에 대한 작업이다. 요소 전체를 대상으로 무언가 하는 메서드를 제공하지 않는 이유는, 가비지 컬렉션의 동작 시점을 정확히 알 수 없기 때문이다. 객체가 모든 참조를 잃었을 때, 즉시 메모리에서 삭제될 수도, 다른 삭제 작업이 있을 때까지 대기하다 삭제될 수도 있다. 현재 위크맵에 요소가 몇 개 있는지 정확히 파악하는 것 자체가 불가능하다.

위크셋도 셋과 유사하지만, 객체만 저장할 수 있다는 점이 다르다. 나머지 특징은 위크맵과 같다.

두 자료구조는 객체엔 '주요' 자료를, 위크맵과 위크셋엔 부수적인 자료를 저장하는 형태로 활용할 수 있다.


참조:
ko.javascript.info
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Set