Deep Dive하기 전에 내가
Map
와{ }
를 어떻게 사용해 왔고, 얼마나 알고 있는지 확인해보자
자바스크립트에는 key ,value 쌍으로 이루어진 데이터를 묶어서 다루는 데이터 형식으로 { }
와 Map
이 있다. { }
와 Map
은 몇 몇 차이를 가지고 있지만, key, value를 저장하는 컬렉션으로 사용할 때는 크게 차이점을 느끼지 못 했다. 보통 해당 키 값을 반복적으로 업데이트 해야 한다면 { }
를 사용했는데, 그 이유는 단순히 코드가 짧아서 이다.
💡
{ }
를 key, value 값을 저장하는 자료구조라고 엄밀하게 부르고 싶을땐, 리터럴 객체라는 단어로 명명하는 편인데, 그 이유는 아래와 같다.typeof [] === 'object' // true typeof null === 'object' // true typeof {} === 'object' // true // typeof 연산자는 피연산자의 타입을 스트링으로 리턴한다. // 리턴하는 타입은 원시타입이다. // []은 실제로 object가 맞다. 🔆
자바스크립트에서 객체는 포괄적인 의미를 갖는다.
객체를 만드는 방법은 여러가지 있지만, 그 중에 리터럴 객체를 이용하는 것이 가장 보편적인 방법이라 리터럴 객체라고 부르는 편이다.const literalObj = { love :'JavaScript'; }; const obj = new Object(); obj.love = 'JavaScript'; // key:value를 집어넣기 힘들다. const obj = Object.create(literalObj); // deep copy할 목적이 아니라면 Object.create를 사용할 이유가 없어 보인다.
상황 설명 : items 배열에 item들이 저장되어 있고, 해당 item의 숫자를 세어 저장하고자 한다.
items = ['a','b','a','c'] => a:2,b:1,c:1
Map
const myMap = new Map();
for (const item of items) {
myMap.has(item)
? myMap.set(item, myMap.get(item) + 1)
: myMap.set(item, 1);
}
{ }
const myObj = {};
for (const item of items) {
myObj[item] = myObj[item] ? myObj[item] + 1 : 1;
}
{ }
를 사용해서 key, value 쌍을 저장하는 코드가 짧다는 것을 확인할 수 있다. 물론, Map
을 사용하는 경우도 있다. { }
의 경우 입력한 key 순서를 보장하지 못 한다. 심지어 { }
는 직접적으로 순회할 수도 없다. 이터러블하지 않기 때문이다. { }
를 순회하기 위해선 이터러블 객체를 반환하는 메서드인 Object.keys, Object.values, Object.entries를 사용해 이터러블을 반환한 후에 순회가 가능하다.
이터러블을 간단히 알아보자
❓ 해당 데이터가 이터러블한 지 확인하는 방법
Symbol.iterator in map // true Symbol.iterator in {} // false
❓ 이터러블 하다면 뭐가 가능할까
- for .. of 문으로 순회
- 스프레드 연산자 사용 가능
- 구조분해 할당 가능
사용 예시
const coin1 = ["이더리움", "300만원"]; const coin2 = ["비트코인", "6000만원"]; const coinMap = new Map([coin1, coin2]); // for .. of 문으로 순회 for (const coinInfo of coinMap){...} // 스프레드 연산자 사용 가능 const coinMapArr = [...coinMap]; console.log(coinMapArr) // [ [ '이더리움', '300만원' ], [ '비트코인', '6000만원' ] ] // 구조분해 할당 가능 for (const [coinName,coinPrice] of coinMap){...}
순회하기 전 key, value 저장하는 로직
const items = [4,3,2,1];
const myMap = new Map();
for (const item of items) {
myMap.has(item) ? myMap.set(item, myMap.get(item) + 1) : myMap.set(item, 1);
};
const myObj = {};
for (const item of items) {
myObj[item] = myObj[item] ? myObj[item] + 1 : 1;
};
{ }의 순회
Object.keys(myObj);
// [ '0', '1', '2', '3' ]
Map의 순회
myMap.keys();
// [Map Iterator] { 4, 3, 2, 1 }
Map
은 모든 값들을 key 로 가질 수 있는데 반해 { }
는 제한이 있다.
리터럴 객체는 string | number | symbol에 해당하는 값만 key로 가질 수 있다.
type keyTypes = keyof any;
// keyTypes = string | number | symbol
🔥주의할 점🔥 은 저장한 모든 key를 반환해서 사용할 수 없다는 점이다.
예시를 통해 알아보자
const 소중한심볼 = Symbol('없어지면큰일남');
const items = [소중한심볼,소중한심볼,소중한심볼];
const myMap = new Map();
for (const item of items) {
myMap.has(item) ? myMap.set(item, myMap.get(item) + 1) : myMap.set(item, 1);
}
const myObj = {};
for (const item of items) {
myObj[item] = myObj[item] ? myObj[item] + 1 : 1;
}
{ }
에 심볼을 key로 사용할 순 있지만, Key로 반환할 순 없다
Object.keys(myObj);
// []
Map은 가능하다.
myMap.keys();
// [Map Iterator] { Symbol(key) }
myMap.forEach((key)=>myMap.get(key));
// Symbol(key)를 key로 myMap.get(key)로 value를 꺼낼 수 있다.
💡 Object.keys는 이터러블 객체를 반환하지만 저장된 모든 key 값을 반환하는 것은 아니다. 주어진 객체의 열거 가능한 속성 중 문자열 로 된 속성 이름만 배열로 반환한다.
Object.values, Object.entries도 프로퍼티가 문자열인 경우만 해당 값을 반환한다.
{ }
와 Map
에 대해 기존에 알고 있던 사실들을 되짚어 보며, 어떤 경우에 뭘 선택하여 사용했는 지 확인했다. 다음 글에서는 Deep Dive하며, 왜 성능상의 차이가 발생하는 지를 알아보자.