Modern Javascript Map, Set

[Modern Javascript] Map, Set


본 내용은 모던 자바스크립트 맵과 셋을 보고 공부, 정리한 내용입니다.

맵 (Map)

자바스크립트의 Map은 key, value 형태로 데이터를 저장한다는 점에서 객체와 유사하지만, Map은 key 데이터형에 제약이 없다는 점에서 객체와 다르다.
객체는 key를 문자형으로 변환하여 저장하고, Map은 key 데이터형 그대로 저장한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 맵 예시
let map = new Map();
const john = {};

map.set("john", 21);
map.set(john, 22);

console.log(map.get("john"));   // 21 
console.log(map.get(john));     // 22
console.log(map.keys());        // [Map Iterator] { 'john', {} }

// 객체 예시
const obj = {};

obj["john"] = 21;
obj[john] = 22;

console.log(obj["john"]);      // 21
console.log(obj[john]);        // 22
console.log(Object.keys(obj)); // [ 'john', '[object Object]' ]

위 예시에서 Map의 key들을 살펴보면 객체 자체가 key로 사용된 반면, 객체의 key들을 살펴보면 객체가 문자형으로 변환된 상태로 사용되었음을 알 수 있다.
Map 또한 Object를 상속하여 만들어졌으므로, 일반적인 객체처럼 데이터 삽입이 가능하다. 하지만 이는 권장되지 않는 방법이다. 일반적인 객체 방식으로 데이터를 관리하게 되면 Map 에서 제공하는 메서드들을 이용할 때 의도치 않은 결과를 낳을 수 있다.

1
2
3
4
5
6
7
8
9
10
11
let map = new Map();

map.set("john", 21);
map.set("matt1", 22);
map.set("apple", 23);
map["matt2"] = 22;

// matt2는 Map.keys 메서드로부터 반환되지 않음
// matt2는 get 메서드로 찾을 수 없음
console.log(map.keys());        // [Map Iterator] { 'john', 'matt1', 'apple' }
console.log(map.get("matt2"));  // undefined

Map과 객체의 차이를 조금 더 살펴보자면

  • 객체는 key가 숫자형, 문자형, 심볼형 순으로 정렬되어 저장된다.
  • Map은 데이터가 삽입된 순서를 유지한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let map = new Map();

map.set("john", 21);
map.set({}, 22);
map.set(25, 23);

console.log(map); // Map { 'john' => 21, {} => 22, 25 => 23 }

let obj = {};
obj["john"] = 14;
obj[25] = 4;
obj[Symbol('id')] = 7;

console.log(obj); // { '25': 4, john: 14, [Symbol(id)]: 7 }

위 코드를 보면 Map은 데이터 삽입 순서를 유지하고 있는 반면, 객체는 숫자형, 문자형, 심볼형 순으로 정렬되어있는 것을 볼 수 있다.
덧붙이자면, 객체는 숫자형의 경우 크기 순으로 정렬되고, 문자형의 경우 삽입된 순서를 유지한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let obj = {};
obj["john"] = 14;
obj["apple"] = 23;
obj["hippo"] = 23;
obj[10000] = 3;
obj[100] = 2;
obj[25] = 4;
obj[Symbol('id')] = 7;

console.log(obj);
/*
출력 결과

{
	'25': 4,
	'100': 2,
	'10000': 3,
	john: 14,
	apple: 23,
	hippo: 23,
	[Symbol(id)]: 7
}

*/

자바스크립트의 Map이 내부적으로 어떤 자료구조를 사용하는지 궁금하여 열심히 구글링 해보았으나, 공식 문서에서는 해당 내용을 찾지 못했고 stackoverflow 질문글의 답변에서 실마리를 찾았다.
자바스크립트의 객체와 Map 모두 간단한 hash를 이용한 배열 구조로 이루어져 있고, 둘 간의 큰 차이는 없지만 hashMap과 같은 자료구조로 사용하기에 더 적합한 건 Map이라고 한다.


Map 요소에 반복 작업하기

Map에서 순회 가능한 객체를 반환받는 메서드는 아래와 같다.

  • Map.keys()
    Map의 key들을 모은 이터러블 객체를 반환한다.

  • Map.values()
    Map의 value들을 모은 이터러블 객체를 반환한다.

  • Map.entries()
    Map의 key, value 쌍을 모은 이터러블 객체를 반환한다.

Map은 이터러블 객체이므로 forEach나 for..of문의 사용이 가능하다.

1
2
3
4
5
6
7
8
// 각 (키, 값) 쌍을 대상으로 함수를 실행
recipeMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // cucumber: 500 ...
});

for (let entry of recipeMap) { // recipeMap.entries()와 동일합니다.
  alert(entry); // cucumber,500 ...
}

객체와 Map 사이 변환

객체 → Map

Map 생성 시 entries로 이루어진 배열을 인자로 넘겨주면, 해당 entries로 만들어진 Map을 얻을 수 있다.

1
2
3
4
5
let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

만약 객체를 Map으로 바꾸고 싶다면, 객체의 key, value 쌍을 entry로 하는 배열을 반환해주는 Object.entries() 내장 메서드를 사용하여 할 수 있다.

1
2
3
4
5
6
7
let obj = {
  name: "John",
  age: 30
};

// Object.entries(obj)는 [['name', 'John'], ['age': 30]] 을 반환한다.
let map = new Map(Object.entries(obj));

Map → 객체

반대로 Map을 객체로 변환하고 싶다면 Object.fromEntries() 내장 메서드를 이용할 수 있다.

1
2
3
4
5
6
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);

let obj = Object.fromEntries(map.entries()); // 맵을 일반 객체로 변환 (*)

Object.fromEntries() 메서드는 인자가 이터러블 객체이면 된다. 즉, 반드시 배열이 아니어도 된다.

1
let obj = Object.fromEntries(map); // .entries()를 생략함

셋 (Set)

Set은 중복을 허용하지 않는 value들을 저장하는 자료구조이다.
Set의 주요 메서드는 아래와 같다.

1
2
3
4
5
6
let set = new Set(iterable) // 생성 방법
set.add(value) // 요소 추가. 이미 존재하면 무시됨.
set.delete(value) // 요소 제거. 제거 성공하면 true, 실패하면 false 리턴
set.has(value) // 요소 찾기. 찾으면 true, 그렇지 않으면 false 리턴
set.clear() // set 비우기
set.size // set 크기 리턴

Set은 중복이 있을 수 없는 자료구조이므로, 중복을 제거할 필요가 있거나 단 한 번만 기록해야하는 작업에 유용하다.
Set도 이터러블 객체이다. 즉, forEach나 for..of문의 사용이 가능하다.

1
2
3
4
5
6
7
8
9
let set = new Set(["oranges", "apples", "bananas"]);

for (let value of set)
	alert(value);

// forEach를 사용해도 동일하게 동작합니다.
set.forEach((value, valueAgain, set) => {
  alert(value);
});

Set의 forEach문을 살펴보면, key를 사용하지 않기 때문에 인자로 value를 두 번 받는다. 비효율적으로 보이지만 Map과의 호환성을 맞추기 위해 이렇게 설정되었고, 이 덕분에 Map → Set, Set → Map 간의 변환이 자유롭다.
Map과의 호환성을 위해 Map에서 지원했던 내장 메서드들도 동일하게 지원한다.

  • Set.keys()
    Set 안의 모든 값을 포함하는 이터러블 객체를 반환한다.

  • Set.values()
    Set.keys()와 동일한 작업을 한다.

  • Set.entries()
    [value, value] entries를 갖는 이터러블 객체를 반환한다.

0%