맵이란?
- 키/밸류 쌍을 저장한다.
- 이 쌍을 합쳐서 엔트리라고도 한다.
- 그래서 엔트리들을 저장한다고도 할 수 있다.
가끔
Map
자료구조가 적합한 곳에 오브젝트를 대신 사용하는데, 이는 좋지 않은 선택이다.
참고자료: Object 와 Map 의 차이
Map
의 탄생
- ES2015+ 에서
맵(Map)
,셋(Set)
,위크맵(WeakMap)
,위크셋(WeakSet)
이 생겼다. Set
은 중복되지 않은 값의 집합을 저장한다.- 위크맵은 키가 객체이고 객체의 참조(strong reference)가 사라지면 가비지콜렉트된다.
- 위크셋은 위크맵의
Set
버전이다.
Map
의 기본 메서드
set()
: 값을 설정할 때 사용한다.clear()
: 맵의 모든 엔트리를 삭제할 때 사용한다.delete()
: 하나의 엔트리를 삭제할 때 사용한다.has()
: 해당 키가 있는지 확인할 때 사용한다.get()
: 해당 키에 대응하는 value 를 얻을 때 사용한다.keys()
: 모든 키를MapIterator
형태로 얻는다.entries()
: 키 밸류를 엔트리 형태로 얻는다.forEach()
:forEach
형태로 순회한다.size
: 내부에 몇개의 값이 있는지 알려준다.
Map
의 메서드는 이름이 너무 명확해서 이름만 봐도 기능을 알기 쉽다.
더 자세한 설명이 필요하다면 공식문서 에서 보자.
Map
과 Object
의 용도 차이 알아보기
- 사실 자바스크립트의 일반 객체를 사용해도 키, 값을 매칭하는 형식으로 사용은 가능하다.
- 그러나
Map
은 기존 자바스크립트 일반 객체와 비교되는 확연한 특징이 있다.
키의 타입 (Type of keys)
Object
는 키에 문자열이나 심볼만 넣을 수 있다.Object.prototype
내부에 있는 프로퍼티를 키의 이름으로 지정하는 경우 프로토타입 사용이 힘들다.Map
은 모든 타입을 다 키로 지정할 수 있다.
// prettier-ignore
const obj = {
0: "a",
"0": "b"
};
const map = new Map();
map.set(0, "a");
map.set("0", "b");
console.log(obj[0]); // b
console.log(obj["0"]); // b
console.log(obj[0] === obj["0"]); // true
console.log(map.get(0)); // a
console.log(map.get("0")); // b
console.log(map.get(0) === map.get("0"));
obj
의 경우, 문자열"0"
과 숫자0
타입을 구분해서 키로 지정할 수 없는 것을 볼 수 있다.map
의 경우, 문자열"0"
과 숫자0
타입을 구분하여 키로 지정할 수 있다.
순회 (Iteration)
Object
는 순서가 때때로 뒤죽박죽이다.- 몇몇 케이스(ex.숫자와 문자를 동시에 키로 이용하는 경우)에 삽입한 순서를 유지하지 못한다.
Map
은 타입에 관계 없이 삽입한 순서를 유지한다.- 삭제했다가 다시 넣으면 순서가 맨 뒤로 간다.
- 단순 값 업데이트 시에는 순서가 변화하지 않는다.
Object
는 자체적으로Iterator
를 제공하지도 않는다.
const obj = {};
obj["a"] = "hello";
obj[0] = "world";
for (const [k, v] of Object.entries(obj)) {
console.log(k, v);
/*
출력결과:
0 world
a hello
*/
}
// 출력결과 삽입한 순서가 아니다.
const map = new Map();
map.set("a", "hello");
map.set(0, "world");
for (const [k, v] of map) {
console.log(k, v);
/*
출력결과:
a hello
0 'world'
*/
}
// 출력결과 삽입한 순서를 지킨다.
최적화 (Optimization)
Object
는 속성이 추가/업데이트 되며 제거되지 않는다는 가정하에 최적화된다.Map
은 사용 사례가 다르게 적용되기 때문에 다른 방법으로 최적화된다.
키 값 존재 확인
키에 대응하는 값으로 해당 키가 존재하는지 확인할 때 둘은 약간의 차이가 있다.
Object
는 키에 대응하는 값으로falsy
한 것이 들어있을 때 실수하기 쉽다.Map
은 키에 대응하는 값으로falsy
한 값이 있어도has()
메서드를 통해 확실히 확인이 가능하다.
const obj = {
a: 0,
};
const map = new Map();
map.set("a", 0);
if (obj.a) {
console.log("obj 에 키 a 존재");
}
if (map.has("a")) {
console.log("map 에 키 a 존재");
}
/*
출력 결과:
map 에 키 a 존재
*/
- 값이
falsy
하기 때문에if(obj.a)
조건이 충족되지 않는다.
사이즈 확인
Object
는enumerable
한 프로퍼티를 세는데 부가적인 작업이 든다.Map
은Map.size
로 한번에 확인이 가능하다.
Map
에서 주의할만한 특성
Object
는 주소값을 기준으로 구분한다.
Object
를 키로써 활용할 수 있는 반면, 속성이 같은Object
여도 주소값이 다르기 때문에 매Object
가 다른 키가 된다.
get
은 두가지 상황에서 undefined
를 반환한다.
- 키에 대해 일치하는 엔트리가 없거나 엔트리가 존재하지만 값이
undefined
인 경우이다. - 그래서 존재를 확인할 때는
has()
메서드를 이용해야 한다.
키 동등성으로 SameValueZero 를 이용한다.
SameValueZero 에 대한 설명은 여기서 확인할 수 있다.
- 간단하게 표현하자면
===
와 동일한데,NaN
의 동등 비교가 가능하다+0
,-0
에 대한 처리도===
와 동일하다.
Map
은 Iterable
이다.
Map
은for-of
와 같은iterator
를 사용하는 문법에서 반복 가능하다.
const m = new Map();
m.set("a", "apple");
m.set("b", "blue");
m.set("c", "chrome");
for (const e of m) {
console.log(e);
}
/*
출력결과:
(2) ['a', 'apple']
(2) ['b', 'blue']
(2) ['c', 'chrome']
*/
m.forEach((value, key) => {
console.log(`${key} => ${value}`);
});
/*
출력결과:
a => apple
b => blue
c => chrome
*/
엔트리의 배열을 이용해 맵 생성하기
- 엔트리란
[key, value]
형태의 자료구조를 말한다. - 엔트리의 배열을
new Map()
생성자에 넣어Map
객체를 생성할 수 있다. - API 에서 JSON 엔트리의 배열 형태로 응답을 받으면 바로
Map
생성자를 통해Map
을 만들어낼 수 있다. - API 에서 JSON 오브젝트 형태로 받았다면,
Object.entries()
를 이용해서 바로Map
을 만들 수 있다.
const m = new Map([
["one", "uno"],
["two", "due"],
["three", "tre"],
]);
for (const e of m) {
console.log(e);
}
/*
출력 결과:
(2) ['one', 'uno']
(2) ['two', 'due']
(2) ['three', 'tre']
*/
const m2 = new Map(
Object.entries({
one: "uno",
two: "due",
three: "tre",
})
);
for (const e of m2) {
console.log(e);
}
/*
출력 결과:
(2) ['one', 'uno']
(2) ['two', 'due']
(2) ['three', 'tre']
*/
사실
Map.prototype.entries()
와Map.prototype[Symbol.iterator]
는 동일한 함수를 참조한다.
맵의 서브클래스 활용해보기
- 맵을 서브클래스로 이용하면 강력한 커스텀 맵을 만들어내는 것이 가능하다.
class MyMap extends Map {
filter(predicateFn, thisArg) {
const newMap = new (this.constructor[Symbol.species] || MyMap)();
for (const [key, value] of this) {
if (predicateFn.call(thisArg, key, value, this)) {
newMap.set(key, value);
}
}
return newMap;
}
}
const m = new MyMap([
["one", "uno"],
["two", "due"],
["three", "tre"],
]);
const filteredMap = m.filter((key) => key.includes("t"));
for (const [key, value] of filteredMap) {
console.log(`${key} => ${value}`);
}
/*
출력결과:
two => due
three => tre
*/
자신의 인스턴스를 얻기 위해
new this.constructor[Symbol.species]
를 활용한 것을 볼 수 있다.
Map
의 성능
- 당연히 일반 객체보다 좋다.
- 스펙상 'O(1)의 접근 시간을 사용하는 해시 테이블 또는 기타 매커니즘을 사용하여 구현해야 한다.' 라고 정의되어 있기 때문에 어떤 JS 엔진에서도 빠르다.
- 아래 코드보다 빨라야 한다.
if (!array.some((e) => e.key === key)) {
array.push({ key, value });
}
반응형
'자바스크립트 > 모던 자바스크립트' 카테고리의 다른 글
모던 자바스크립트, 위크 맵(WeakMap) 과 위크 셋(WeakSet) (0) | 2023.03.24 |
---|---|
모던 자바스크립트, 셋 혹은 세트 (Set) (0) | 2023.03.23 |
모던 자바스크립트, 4가지 동등성 비교 알고리즘 (0) | 2023.03.22 |
모던 자바스크립트, Reflect (리플렉트) 객체란? (0) | 2023.03.20 |
모던 자바스크립트, TypedArray (타입이 있는 배열) (0) | 2023.03.19 |