Map객체에이어 WeakMap객체라는 것이 있다.
이름은 비슷하지만 용도는 전혀 다른것임을 유의하자.
WeakMap은 가비지콜렉션과 밀접한 관계가 있는 객체다.
( Map객체는 가비지콜렉션과 전혀 관계가 없다. )
WeakMap객체는 키로 오직 객체만 사용할 수 있다.
let john = { name: "John" };
let m1 = new Map();
m1.set(john,{age:32, address:'.....'});
변수 john은 객체를 참조하고 있다. 이 객체가 메모리해제되는 방법은 john변수가 다른 객체를 참조해야 한다.
맵객체에 키로 john이 사용되고 값도 할당된다. 여기서 값은 어떤 유형의 데이터라도 상관없다.
이러한 방법을 사용하는 것은 john객체에 프로퍼티를 추가하기 곤란한 상황일때 사용하는데 맵객체에 john을 키로 어떠한 값을 추가하면 john객체에 프로퍼티를 추가한것과 같은 효과가 있다. 맵객체에 john만의 값을 추가하여 언제든지 조회가 가능하기 때문이다.
이제 맵객체는 john의 프로퍼티를 가지게 되는데 맵객체에 추가된 프로퍼티가 john객체를 참조하게 되고 john객체는 2개의 변수가 참조하게 되었다.
하나는 john변수고 다른 하나는 맵객체의 프로퍼티다.
john객체가 메모리해제되려면 john과 맵프로퍼티가 모두 참조를 하지않아야 한다.
어느 시점에 john객체가 더이상 쓰이지 않아 메모리해제를 해야할 경우 john변수만 null을 할당하고 맵프로퍼티 참조해제를 잊어버리면 john객체는 가비지콜렉션대상이 되지 않는다.
자바스크립트는 이러한 상황을 대비해 WeakMap이란 객체를 지원한다.
WeakMap객체에 프로퍼티를 추가하고 john변수의 참조를 끊으면 엔진이 자동으로 john객체를 참조하는 WeakMap프로퍼티를 삭제하여 john객체는 가비지콜렉션대상이 된다. (메모리해제되는 시점은 엔진이 정한다.)
WeakMap객체가 지원하는 메소드
weakMap.get(key);
weakMap.set(key, value);
weakMap.delete(key);
weakMap.has(key)
WeakMap객체는 메모리해제되는 시점을 알수없기때문에 keys(), values(), entries()와 같은 콜렉션메소드를 지원하지 않는다.
WeakSet객체는 키로 오직 객체만 사용할 수 있다.
WeakSet객체를 만들고 원소로 객체를 추가하면 WeakSet객체가 원소객체를 참조하게 된다.
객체원소를 참조하는 변수가 참조를 해제하면 WeakSet객체도 원소객체참조를 해제하여 원소객체는 가비지콜렉션대상이 된다.
WeakMap객체 활용
기존의 웹브라우저 어플리케이션은 페이지이동기반이었기 때문에 페이지가 갱신될때마다 메모리해제되어 WeakMap객체를 사용하는 경우가 드물었다. 그러나 SPA의 경우에는 WeakMap객체를 활용해야 한다.
서드파티라이브러리 객체에 프로퍼티를 추가해야 할때 weakmap을 사용할 수 있다.
서드파티객체에 임의로 프로퍼티를 추가하면 나중에 충돌할 수 있어 고유한 심볼프로퍼티를 사용해야 한다.
또다른 방법으로 weakmap을 사용하면 같은 효과를 얻을 수 있다.
WeakMap객체에 서드파티객체를 키로하는 프로퍼티를 추가하고 필요한 값을 설정하면 된다.
더구나 서드파티객체를 참조하는 변수만 해제하면 WeakMap객체에 추가된 프로퍼티도 같이 메모리해제된다.
사용자 방문시 카운터를 증가시킬때
function counterVisit(user){
let countNum = visitUser.get(user) || 0;
visitUser.set(user,countNum+1);
}
let visitUser = new WeakMap();
let john = { name: "John" };
counterVisit(john);
사용자가 방문했다면 사용자정보객체를 만들고 john변수로 참조하게 한다.
방문횟수를 저장하기위한 객체를 WeakMap객체로 키는 사용자정보객체로하고 값은 방문횟수로 하면 언제든 사용자객체로 방문횟수를 알수있을뿐아니라 사용자가 떠나면 john변수만 참조를 해제하면 된다.
function getUserData(userObj){
if(wm.has(userObj)){
console.log('캐싱데이터');
return wm.get(userObj);
}
console.log('서버로부터 데이터를 가져온다');
let receiveData = 'asdfasdfasdfasdf';
wm.set(userObj,receiveData);
return receiveData;
}
let wm = new WeakMap();
let john = {id:'john'};
console.log(getUserData(john));
console.log(getUserData(john));
john=null;
2번, 3번에서 의문이 제기된다.
john 객체에 직접 필요데이터를 프로퍼티로 추가하면 WeakMap객체필요없이 메모리해제가 바로되는데 왜 WeakMap객체를 사용해야 하나?
john객체가 서드파티에서 제공되거나 관리주체가 따로 있는 경우에는 WeakMap을 사용한다.
메세지관리 WeakSet
let messages = [
{ text: "Hello", from: "John" },
{ text: "How goes?", from: "John" },
{ text: "See you soon", from: "Alice" },
];
let readSet = new WeakSet();
readSet.add(messages[0]);
readSet.add(messages[1]);
console.log(readSet.has(messages[1]));
messages.shift();
for (const val of messages) {
console.log(val.text);
}
메세지객체배열이 있고 이 배열은 외부에서 관리하기때문에 각 메세지객체에 속성을 추가하거나 수정하지 않아야한다.
메세지를 읽음을 관리해야 할때 위와같이 읽음 WeakSet객체를 만들고 메세지가 이객체에 추가되었다면 읽음상태임을 알수 있다.
메세지객체 배열은 외부에서 관리하기때문에 메세지객체가 수시로 추가되거나 삭제될 수 있다.
전체 메세지들중에 읽은메세지들을 추적하려면 메세지배열을 반복순회하면서 WeakMap객체에 있는지 확인해야 한다.
WeakMap객체에 추가된 프로퍼티들은 가비지콜렉션대상일수 있기 때문에 이객체를 반복순회하면 안된다.
메세지배열에 변수객체가 아니고 리터럴객체가 있기때문에 WeakMap객체에 추가할때 리터럴객체가 복사되어 추가되지 않는가? 하는 의문이 있을 수 있다.
readSet.add(messages[0]);와 같이 messages[0]식별자로 접근되어 메모리번지값만 사용되므로 객체복사되지 않는다.