모던 JavaScript 튜토리얼을 따라가면서 정리합니다.
위크맵
맵에서 객체를 키로 사용한 경우 맵이 메모리에 있는 한 객체도 메모리에 남는다. 가비지 컬렉터의 대상이 아니다.
1 2 3 4 5 6 7 8 9 10 11 12
let john = { name: "John" }; let map = new Map(); map.set(john, "..."); john = null; // 참조를 null로 덮어씀 // john을 나타내는 객체는 맵 안에 저장되어 있다. // map.keys()를 이용하면 해당 객체를 얻는 것도 가능하다. for (let obj of map.keys()) { alert(JSON.stringify(obj)); } alert(map.size())
그러나 위크맵을 사용하면 키로 사용된 객체를 참조하는 것이 아무것도 없다면 해당 객체는 메모리와 위크맵에서 자동으로 삭제된다.
1 2 3 4
let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); john = null;
위크맵
은맵
과 유사한 컬렉션이다.위크맵
을 구성하는 요소의 키는 오직 객체만 가능하다. 키로 사용된 객체가 메모리에서 삭제되면 이에 대응하는 값 역시 삭제된다.위크맵이 지원하는 메서드:
get
,set
,delete
,has
위크맵이 적은 메서드만 지원하는 이유: 가비지 컬렉션 때문이다. 객체는 모든 참조를 잃게 되면 자동으로 가비지 컬렉션의 대상이 되는데 가비지 컬렉션의 동작 시점은 정확히 알 수 없다. 현재 위크맵에 요소가 몇 개 있는지 파악하는 것도, 위크맵의 요소 전체를 대상으로 무언가를 하는 동작 자체도 불가능한 이유이다. 가비지 컬렉터가 한 번에 메모리를 청소할 수도 있고, 부분 부분 메모리를 청소할 수도 있기 때문이다.
위크맵의 유스케이스
추가 데이터: 부차적인 데이터를 저장할 곳이 필요할 때
1 2
weakMap.set(john, "비밀문서"); // john이 사망하면, 비밀문서는 자동으로 파기된다
맵을 사용해 사용자 방문 횟수를 저장한다고 해보자. 어떤 사용자의 정보를 저장할 필요가 없어지면(가비지 컬렉션의 대상이 되면) 해당 사용자의 방문 횟수도 저장할 필요가 없어진다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 📁 visitsCount.js let visitsCountMap = new Map(); // 맵에 사용자의 방문 횟수를 저장함 // 사용자가 방문하면 방문 횟수를 늘려줍니다. function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } // 📁 main.js let john = { name: "John" }; countUser(john); // John의 방문 횟수를 증가시킵니다. // John의 방문 횟수를 셀 필요가 없어지면 아래와 같이 john을 null로 덮어씁니다. john = null;
null로 덮어줘도 visitsCountMap의 키로 사용되고 있어서 메모리에서 삭제되지 않는다. 우리가 손수 지워주지 않으면
visitsCountMap
은 한없이 커질텐데, 앱이 복잡할 땐 이렇게 쓸모 없는 데이터를 수동으로 바꿔주는 것이 골치아프다. 이때 위크맵을 사용하면 수동으로 청소해줄 필요가 없다.캐싱: 시간이 오래 걸리는 작업의 결과를 저장해서 연산 시간과 비용을 절약해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// 📁 cache.js let cache = new Map(); // 연산을 수행하고 그 결과를 맵에 저장합니다. function process(obj) { if (!cache.has(obj)) { let result = /* 연산 수행 */ obj; cache.set(obj, result); } return cache.get(obj); } // 함수 process()를 호출해봅시다. // 📁 main.js let obj = {/* ... 객체 ... */}; let result1 = process(obj); // 함수를 호출합니다. // 동일한 함수를 두 번째 호출할 땐, let result2 = process(obj); // 연산을 수행할 필요 없이 맵에 저장된 결과를 가져오면 됩니다. // 객체가 쓸모없어지면 아래와 같이 null로 덮어씁니다. obj = null; alert(cache.size); // 1 (엇! 그런데 객체가 여전히 cache에 남아있네요. 메모리가 낭비되고 있습니다.)
반면 위크맵을 사용하면 캐싱된 데이터 역시 메모리에서 삭제될 것이고, cache에 어떤 요소도 남지 않을 것이다. 단, size 프로퍼티는 이용할 수 없다.
위크셋
위크셋
은셋
과 유사한 컬렉션이다. 위크셋엔 객체만 저장할 수 있다. 위크셋에 저장된 객체가 도달 불가능한 상태가 되면 해당 객체는 메모리에서 삭제된다.위크셋엔 위크맵처럼 복잡한 데이터를 저장하지 않는다. 대신 ‘예’나 ‘아니오’ 같은 간단한 답변을 얻는 용도로 사용된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
let visitedSet = new WeakSet(); let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; visitedSet.add(john); // John이 사이트를 방문합니다. visitedSet.add(pete); // 이어서 Pete가 사이트를 방문합니다. visitedSet.add(john); // 이어서 John이 다시 사이트를 방문합니다. // visitedSet엔 두 명의 사용자가 저장될 겁니다. // John의 방문 여부를 확인해보겠습니다. alert(visitedSet.has(john)); // true // Mary의 방문 여부를 확인해보겠습니다. alert(visitedSet.has(mary)); // false john = null; // visitedSet에서 john을나타내는 객체가 자동으로 삭제됩니다.
두 자료구조 모두 구성 요소 전체를 대상으로 하는 메서드를 지원하지 않는다. 구성 요소 하나를 대상으로 하는 메서드만 지원한다.
객체에는 ‘주요’ 자료를, 위크맵
과 위크셋
에는 ‘부수적인’ 자료를 저장하는 형태로 위크맵과 위크셋을 활용할 수 있다. 객체가 메모리에서 삭제되면, 그리고 오로지 위크맵과 위크셋의 키만 해당 객체를 참조하고 있다면 위크맷이나 위크셋에 저장된 연관 자료들 역시 메모리에서 자동으로 삭제된다.