Map 자료구조는 객체 리터럴(literal)과 비슷해보인다. key-value 쌍을 이용하는 자료구조이고 입력한 순서대로 순회도 할 수 있다. 하지만 둘은 차이가 있다.
string
, symbol
의 키만 가능하다.const iAmMap = new Map();
const iAmObj = {}
// const keyFunc = function(){};
iAmMap.set(function(){}, 1)
iAmObj[function(){}] = 1;
console.log(iAmMap) // Map(1) {f => 1}
console.log(iAmObj) // {function(){}: 1}
for...of
로 순회가 가능하지만 객체 리터럴은 for...in
, Object.keys
, Object.values
로 순회가능하다.const iAmMap = new Map();
const iAmObj = {}
iAmMap.set(1, 1)
iAmMap.set(2, 2)
iAmMap.set(3, 3)
iAmObj[1] = 1;
iAmObj[2] = 2;
iAmObj[3] = 3;
for (const entry of iAmMap) {
console.log(entry);
}
for (const key in iAmObj) {
console.log(key,iAmObj[key]);
}
const iAmMap = new Map();
const iAmObj = {}
for (let index = 0; index < 10_000_000; index++) {
iAmMap.set(index, index)
iAmObj[index] = index;
}
console.time('map delete')
for (const [key] of iAmMap) {
iAmMap.delete(key)
}
console.timeEnd('map delete')
console.time('obj delete')
for (const key in iAmObj) {
delete iAmObj[key]
}
console.timeEnd('obj delete')
const iAmMap = new Map();
const iAmObj = {}
for (let index = 0; index < 10_000_000; index++) {
iAmMap.set(index, index)
iAmObj[index] = index;
}
let count = 0;
for (const key in iAmObj) {
if (Object.hasOwnProperty.call(iAmObj, key)) {
count++;
}
}
console.log(iAmMap.size)// 10000000
console.log(count) // 10000000
Map의 키는 모든 타입이 가능하지만, WeakMap은 Primitive
타입은 올 수 없다. WeakMap의 키는 오직 object만 가능하다.
WeakMap을 사용하는 이유는 GC(Garbage Collection)에 의해 키가 수거 된다는 점이다. 아래 예를 보자
const iAmMap = new Map();
let funcKey = function(){}
iAmMap.set(funcKey, 1);
funcKey = function(){console.log('hi')} // 키 변경
console.log(iAmMap.get(funcKey)) // undefined
console.log(iAmMap) // Map(1) {function(){} => 1}
// funcKey가 변경되어도 iAmMap은 function(){}를 참조하고 있기 때문에 GC에 의해 수거되지 않는다.
Map은 funcKey
가 변경되어도 iAmMap
객체에는 function(){}
키가 살아있지만 접근할 수 없는 상황이 된다. 즉, 이러한 참조가 남아 GC에 의해 수거되지 않는다.
const iAmWeakMap = new WeakMap();
let funcKey = function(){}
iAmWeakMap.set(funcKey, 1);
funcKey = function(){console.log('hi')} // 키 변경
console.log(iAmWeakMap.get(funcKey)) // undefined
console.log(iAmWeakMap) // WeakMap(1) {function(){} => 1}
// funcKey가 변경되면 iAmWeakMap의 키는 GC에 의해 수거된다.
funcKey
를 변경하였더니 시간이 흐르고 GC에 의해 수거되었다.
const countVisitMap = new WeakMap();
const countUser = (user) => {
const count = countVisitMap.get(user) || 0;
countVisitMap.set(user, ++count);
}
const user = {name: 'park'}
countUser(user);
user = null; // 방문자 떠났다.
유저의 방문 횟수를 저장하는 자료구조로 Map을 사용한다면 user가 null
이 되었을 때 메모리 누수가 발생한다. WeakMap을 사용한다면 user가 null
이 되었을 때 GC에 의해 메모리가 수거되므로 메모리 문제에 신경쓰지 않아도 된다.
캐싱은 필요한 데이터를 미리 저장해두고 빠르게 데이터를 제공하는 방식이다.
let cache = new WeakMap();
// 연산 후에 캐싱
function process(obj) {
if (!cache.has(obj)) {
let result = /* 연산 수행 */ obj;
cache.set(obj, result);
}
return cache.get(obj);
}
let obj = {/* ... 객체 ... */};
let result1 = process(obj);
let result2 = process(obj);
// 객체가 쓸모없어지면 아래와 같이 null로 변경
obj = null;
WeakMap을 사용하여 캐싱 해둔 데이터가 사라져도 따로 처리할 것 없이 GC에 의해 메모리가 수거되므로 편한사용이 가능하다.
WeakMap은 일시적으로 관리되는 데이터를 저장하기 위한 저장소라고 생각한다. 계속 유지되는 데이터가 아닌, 잠시 유지되는 데이터에 대한 처리를 할 때 사용한다면 GC에 의해 자연스럽게 수거되기 때문에 메모리 문제에 대해 고민하지 않아도 되서 편한것 같다.