...
자바스크립트는 객체와 배열이라는 강력한 자료구조를 제공합니다.
- 객체 – 키가 있는 컬렉션을 저장함
- 배열 – 순서가 있는 컬렉션을 저장함
하지만 현실 세계를 반영하기엔 이 두 자료구조 만으론 부족해서 맵(Map)과 셋(Set)이 등장하게 되었습니다.
Map 자료형
키가 있는 데이터를 저장한다는 점에서 객체와 유사합니다.
다만, 맵은 키에 다양한 자료형을 허용한다는 점에서 차이가 있습니다.
(객체의 key는 항상 스트링형태로 저장됩니다.)
let map1 = new Map([ // 2차원 key, value 형태의 배열
['a',1],
['a1',2],
['b',3]
])
// map 자료형 : {"a" => 1, "a1" => 2, "b" => 3}
new Map() – 맵을 만듭니다.
map.set(key, value) – key를 이용해 value를 저장합니다.
map.get(key) – key에 해당하는 값을 반환합니다. key가 존재하지 않으면 undefined를 반환합니다.
map.has(key) – key가 존재하면 true, 존재하지 않으면 false를 반환합니다.
map.delete(key) – key에 해당하는 값을 삭제합니다.
map.clear() – 맵 안의 모든 요소를 제거합니다.
map.size – 요소의 개수를 반환합니다.
let map = new Map();
map.set('1', 'str1'); // 문자형 키
map.set(1, 'num1'); // 숫자형 키
map.set(true, 'bool1'); // 불린형 키
/* 2차원 배열 형태로 한방에 선언 할 수 있어요
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
])*/
// 객체는 키를 문자형으로 변환한다는 걸 기억하고 계신가요?
// 맵은 키의 타입을 변환시키지 않고 그대로 유지합니다. 따라서 아래의 코드는 출력되는 값이 다릅니다.
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
맵의 key에 중복값이 있으면 나중값으로 적용됩니다. (이는 객체도 마찬가지)
let map1 = new Map([
['a',1],
['a',2],
['b',3]
]) // {"a" => 2, "b" => 3}
let object1 = {
a:1,
a:2,
b:3
} // {a: 2, b: 3}
const errorMessageObj = {
404 : "페이지가 없습니다",
500 : "서버 오류입니다",
401 : "권한이 없습니다"
}
errorMessageObj.404 // unexpected number 에러. key값은 문자열 처리가 되어서 문자열로 접근해야함
errorMessageObj["404"] // '페이지가 없습니다' 정상접근
const errorMessageMap = new Map([
[404, "페이지가 없습니다"],
[500, "서버 오류입니다"],
[401, "권한이 없습니다"],
])
errorMessageMap.get(404) // '페이지가 없습니다' 따로 문자열 처리없이, 정상접근
맵은 객체와 달리 키를 문자형으로 변환하지 않습니다. 키엔 자료형 제약이 없습니다.
map[key]는 Map을 쓰는 바른 방법이 아닙니다.
map[key] = 2로 값을 설정하는 것 같이 map[key]를 사용할 수 있긴 합니다. 하지만 이 방법은 map을 일반 객체처럼 취급하게 됩니다. 따라서 여러 제약이 생기게 되죠.
map을 사용할 땐 map전용 메서드 set, get 등을 사용해야만 합니다.
맵은 키로 객체도 허용합니다.
let john = { name: "John" };
// 고객의 가게 방문 횟수를 세본다고 가정해 봅시다.
let visitsCountMap = new Map();
// john을 맵의 키로 사용하겠습니다.
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
다음은 guestArr 배열에 이름과 살고있는 도시가 저장되어 있는 자료들이 저장되어 있습니다.
이를 도시별로 묶어 새로 자료를 저장하려고 합니다.
let guestArr = [
{name:"A", city:"Seoul"},
{name:"B", city:"Busan"},
{name:"C", city:"Seoul"},
{name:"D", city:"Sejong"},
{name:"E", city:"Busan"},
{name:"F", city:"Sejong"},
{name:"G", city:"Dawgeon"},
{name:"H", city:"Sejong"},
{name:"I", city:"Dawgeon"},
{name:"J", city:"Busan"},
{name:"K", city:"Seoul"},
]
let Oguest = {};
guestArr.forEach(item => {
if (!Oguest[item.city]) Oguest[item.city] = [];
Oguest[item.city].push(item);
});
console.log("[Object] : ", Oguest);
forEach()를 사용해서 기존 객체 저장법을 사용하면 이렇게 됩니다.
먼저 새로운 객체 Oguest에 해당 도시명 item.city가 없으면 = [] 배열을 새로 넣어줍니다.
그리고 push()를 통해 객체를 넣어줍니다.
여기서 key값이 될 item.city는 스트링이 저장된 변수형태 인데,
이를 객체에 받아오기 위해선, Oguest.item.city = [] 형태로 할수는 없습니다. 왜냐하면 객체 key에는 변수가 올수 없고 무조건 스트링만 오기 때문이죠.
그래서 배열형태로 접근해서 인덱스에 넣는 식으로 해야 합니다. Oguest[item.city] = []
하지만 Map을 쓰면 좀더 직관적으로 사용할 수 있습니다.
map.has()로 키값을 검사하고, 없으면 map.set()을 통해 key와 value를 넣어줍니다.
그리고 map.get으로 key를 가져와 value에 있는 배열에 push() 해줍니다.
let Mguest = new Map();
guestArr.forEach(item => {
if (!Mguest.has(item.city)) Mguest.set(item.city, []);
Mguest.get(item.city).push(item);
});
console.log("[Map] : ", Mguest);
* 체이닝
map.set을 호출할 때마다 맵 자신이 반환됩니다.
이를 이용하면 map.set을 '체이닝(chaining)'할 수 있습니다.
map.set('1', 'str1') .set(1, 'num1') .set(true, 'bool1');
맵의 요소에 반복 작업하기
다음 세 가지 메서드를 사용해 맵의 각 요소에 반복 작업을 할 수 있습니다.
map.keys() – 각 요소의 키를 모은 반복 가능한(iterable, 이터러블) 객체를 반환합니다.
map.values() – 각 요소의 값을 모은 이터러블 객체를 반환합니다.
map.entries() – 요소의 [키, 값]을 한 쌍으로 하는 이터러블 객체를 반환합니다. 이 이터러블 객체는 for..of반복문의 기초로 쓰입니다.
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// 키(vegetable)를 대상으로 순회합니다.
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// 값(amount)을 대상으로 순회합니다.
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// [키, 값] 쌍을 대상으로 순회합니다.
for (let entry of recipeMap) { // recipeMap.entries()와 동일합니다.
alert(entry); // cucumber,500 ...
}
맵은 삽입 순서를 기억합니다. (이터러블 객체)
맵은 값이 삽입된 순서대로 순회를 실시합니다.
객체가 프로퍼티 순서를 기억하지 못하는 것과는 다릅니다.
여기에 더하여 맵은 배열과 유사하게 내장 메서드 forEach도 지원합니다.
// 각 (키, 값) 쌍을 대상으로 함수를 실행
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 ...
});
객체 -> 맵 으로 바꾸기
평범한 객체를 가지고 맵을 만들고 싶다면 내장 메서드 Object.entries(obj)를 활용해야 합니다.
이 메서드는 객체의 키-값 쌍을 요소([key, value])로 가지는 배열을 반환합니다.
// 그냥 맵 만들기 (각 요소가 [키, 값] 쌍인 배열)
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
// 객체로 맵 만들기
let obj = {
name: "John",
age: 30
};
let map2 = new Map(Object.entries(obj)); // 객체를 2차원의 키:밸류 형태로 만들고 맵으로 변환
// [ ["name","John"], ["age", 30] ]
alert( map2.get('name') ); // John
Object.entries를 사용해 객체 obj를 배열 [ ["name","John"], ["age", 30] ]로 바꾸고, 이 배열을 이용해 새로운 맵을 만들어보았습니다.
맵 -> 객체 로 바꾸기
이 반대인 맵을 객체로 바꾸는 방법에 대해 알아보겠습니다.
Object.fromEntries를 사용하면 가능합니다. 이 메서드는 각 요소가 [키, 값] 쌍인 배열을 객체로 바꿔줍니다.
자료가 맵에 저장되어있는데, 서드파티 코드에서 자료를 객체형태로 넘겨받길 원할 때 이 방법을 사용할 수 있습니다.
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); // 맵을 일반 객체로 변환 (*)
// let obj = Object.fromEntries(map); // .entries()를 생략할 수 있음.
// 맵이 객체가 되었습니다!
// obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2
Reference
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.