Object, Map, WeakMap 적절히 쓰기
JS의 객체 형식으로, 키-값 쌍 형식의 데이터를 표현할 수 있다.
JS에서 대부분의 내장 객체와 자료구조들은 Object의 인스턴스라고 할 수 있다. (심지어 Array마저도 내부적으론 Object를 상속받았다)
마찬가지로 키-값 쌍의 데이터를 표현하며, ES6 표준으로 추가되었다.
키의 타입을 Object로만 설정 가능하고, 키를 순회하거나 검색할 수 없는 자료구조. ES6에 마찬가지로 추가되었다.
Property를 컴파일 시점에 알 수 없는 경우
JS의 Object는 '히든 클래스' 기법을 통해 동일한 Property 형태를 가진 Object들에 대한 접근을 최적화한다.
이는 곧 Object가 키-값이 계속해서 바뀌는 상황에는 최적화되어 있지 않음을 의미하기도 한다.
정형화된 Object 형태의 인스턴스는 이 기법으로 효과적이지만, 빈번히 형태가 바뀐다면 오히려 아무런 효과도 없을 것이다.
만약 어떤 데이터의 Id 값에 대응되는 값을 런타임 시간에 추가하는 등, 형태가 정형화되어 있지 않다면 Map 구조를 선호하자.
Property 순회
Object의 키, 값, 또는 키-값의 쌍을 순회해야 한다면, 여러 방식이 존재한다.
// #1, for in
for (const key in obj) {
}
// #2, Object.keys()
Object.keys(obj).forEach((key) => {
})
// #3, Object.entries()
Object.entries(obj).forEach(([key, val]) => {
})
Object는 키-값 형태를 선형적인 배열 형태로 저장하지 않기 때문에, 직접적으로 순회할 방법은 없다는 것이 단점이다.
Object 내의 키들을 전부 찾아낸 후에 배열로 만들어서 순회해야 한다면 그 과정의 오버헤드 또한 크다.
// #1, key-val iter
for (const [key, val] of map) {
}
// #2, keys iter
for (const key of map.keys()) {
}
// #3, forEach
map.forEach((val, key) => {
})
Map은 키-값을 먼저 저장된 순으로 순회하는 Iterater 형식을 제공한다.
Map과 Object의 순회 성능을 비교하려면 이 벤치마크를 참고할 수 있겠다.
객체에 대한 데이터 외부적으로 기록하기
DOM에 버튼들이 있고, 각 버튼이 몇 번 클릭되었는지 저장할 수 있도록 구현해본다고 가정하자
버튼의 클릭 횟수를 저장해야 하지만, 직접 DOM 객체의 프로퍼티로 넣기엔 연관성이 없고 애매하다.
따라서 각 DOM 객체에 대해서 횟수값을 저장하는 Map 자료구조를 써볼 것이다.
const btn = Document.getElementById(...);
const clickCountMap = new Map();
clickCountMap.set(btn, 0);
btn.addEventlistener('click', (e) => {
const btnDOM = e.currentTarget;
// 이전 값을 가져오고 나서 +1 한 값을 저장한다
const prevCnt = clickCountMap.get(btnDOM);
clickCountMap.set(btnDOM, prevCnt + 1);
})
이렇게 한다면 객체를 직접 수정하지 않고 객체에 대한 외부적인 값을 저장할 수 있게 된다.
다만 여기서 Map의 키로 객체를 설정한 것이 문제 상황이 된다.
JS에서 객체는 해당 객체의 참조가 모두 없어져 접근할 수 없는 상태라면 가비지 컬렉션된다.
여기에서 Map은 객체에 대해 부가적인 정보를 저장하는 역할일 뿐이기에, 객체 자체에 접근할 수 없다면 이를 키로 조회할 수도 없다는 것이고, 결국 이에 대한 값도 필요가 없어진다.
하지만 Map이 키로서 객체를 계속해서 참조하고 있기 때문에, 객체가 실질적으로 필요 없어지더라도 가비지 컬렉션이 되지 않는다.
이런 상황에선 '약한 참조'를 통해, 참조를 하더라도 가비지 컬렉션이 가능한 상황을 만들어줘야 한다
이를 구현한 것이 'WeakMap' 이다.
WeakMap을 활용하면 객체에 대해서 외부적인 데이터 (즉 클릭 횟수)를 참조를 통해 저장하고도, 이것이 가비지 컬렉션이 되도록 할 수 있다.
이런 예시 상황같이 객체에 대한 외부적인 값을 저장하고, 이로써 객체를 직접 수정하지 않고 확장하는 경우에도 사용할 수 있다.