Skip to Content
JavaScriptweakmap 약한 참조 알아보기

weakmap 약한 참조 알아보기

WeakMap이란 객체를 키로 사용하며, 키로 사용된 객체가 다른 곳에서 참조되지 않을 때 자동으로 가비지 컬렉션 될 수 있는 자바스크립트의 자료구조 입니다.

객체와 연결된 데이터를 메모리에 안전하게 저장하고, 객체가 더 이상 필요하지 않을 때 자동으로 데이터를 정리하는 용도로 사용됩니다.

Map과 차이점

  • 키는 객체만 사용할 수 있습니다. 문자열이나 숫자는 사용할 수 없습니다.

  • 기존 Map과는 다르게 get, set, delete, has 4개의 메소드만 사용할 수 있습니다.

    • size로 몇 개의 데이터를 가지고 있는지 확인하거나 키를 순회하는 등을 위해서는 객체가 계속 메모리를 차지(강한 참조)하고 있어야 하지만 Weakmap은 약한 참조를 사용하기 때문입니다.
const weakMap = new WeakMap(); let obj1 = { key: "value" }; let obj2 = { key: "value" }; weakMap.set(obj1, "value1"); weakMap.set(obj2, "value2"); console.log(weakMap.get(obj1)); // "value1" console.log(weakMap.has(obj2)); // true obj2 = null; console.log(weakMap.has(obj2)); // false weakMap.delete(obj1); weakMap.has(obj1); // false console.log(obj1); //{key: 'value'}

약한 참조

약한 참조란, 해당 객체가 다른 곳에서 더 이상 사용되지 않는다면 가비지 컬렉터에 의해 자동으로 메모리에서 제거될 수 있도록 하는 참조입니다.

일반적인 객체나 Map에 저장한 경우, 더 이상 데이터에 접근할 수 없다고 해도 가비지 컬렉션 대상이 되지 않고 메모리에 남아있게 됩니다.

let data = { name: "hello" }; const map = new Map(); map.set(data, 1); data = null; console.log(data); // null console.log(map); // Map(1) { { name: 'hello' } => 1 } (데이터가 남아있음)

이는 사용하지 않는 데이터이지만, 메모리에는 존재하게 되어 메모리 누수 문제를 일으킬 수 있습니다.

언제 WeakMap을 사용할까?

실제로 WeakMap을 사용하는 사례를 보면서, 언제/왜 사용하는지 알아보겠습니다.

메모리 누수 방지

카드 UI를 DOM에 추가하고, 추가 데이터를 Map에 저장하는 예제 입니다.

DOM에서는 제거 되었지만, Map에는 데이터가 남아 메모리 누수 문제가 있습니다.

직접 돔을 추가하고, 제거하면서 크롬 개발자 도구를 통해 메모리 사용량을 확인해보겠습니다.

메모리 사용량을 직관적으로 볼 수 있도록, 카드 만들 때 10MB 크기의 Uint8Array를 같이 사용했습니다.

  • “Create Profile Card” 버튼을 클릭하면 카드를 추가하고, Map에 객체 정보를 추가합니다.
  • “Close” 버튼을 누르면 카드를 제거합니다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Profile Card Generator</title> </head> <body> <button id="createCard">Create Profile Card</button> <script> const profileDataMap = new Map(); function createProfileCard(userData) { const card = document.createElement("div"); card.className = "profile-card"; card.innerHTML = ` <h3>${userData.name}</h3> <p>${userData.description}</p> <button class="close-btn">Close</button> `; profileDataMap.set(card, userData); card.querySelector(".close-btn").addEventListener("click", function () { card.remove(); }); document.body.appendChild(card); } document .getElementById("createCard") .addEventListener("click", function () { const names = ["John Doe", "Jane Smith"]; const descriptions = ["Front-end Developer", "Back-end Specialist"]; const index = Math.floor(Math.random() * names.length); const buffer = new Uint8Array(10 * 1024 * 1024); // 10MB createProfileCard({ name: names[index], description: descriptions[index], buffer, }); }); </script> </body> </html>

Map 메모리 사용량 확인해보기

카드를 1개 만들었을 때, 예상한대로 Map의 크기는 약 10MB입니다.

예제

추가로 카드를 1개 더 만들었을 떄 Map의 크기는 약 21MB입니다.

예제

정상적으로 메모리에 할당 된 것 같으니, 이제 DOM을 삭제해보겠습니다.

예제

화면상으로는 카드가 사라지고, DOM에서 해제 되었지만, profileDataMap에서 데이터를 삭제하는 코드가 없기 때문에 profileDataMap에는 userData가 남아있어 메모리 사용량이 변하지 않았습니다.

예제

즉, Map은 강한 참조를 가지고 있어서 keycard가 삭제되었어도 profileDataMap에는 데이터가 그대로 남아 있습니다.

WeakMap 메모리 사용량 확인해보기

이번에는 WeakMap으로 동일하게 테스트 해보겠습니다. 11번 라인에 있는 Map()WeakMap()으로 바꿔주었습니다. 이번에는 메모리 사용량만 바로 확인해보겠습니다.

  • 카드 1개 추가 (WeakMap 크기 약 10MB)

예제

  • 카드 1개 추가 (WeakMap 크기 약 21MB)

예제

  • 카드 1개 추가 (WeakMap 크기 약 31MB)

예제

  • 카드 1개 제거 (WeakMap 크기 약 21MB)

예제

  • 카드 1개 제거 (WeakMap 크기 약 10MB)

예제

WeakMap은 약한 참조를 가지고 있어서, 데이터를 삭제하는 코드가 없어도 keycard가 삭제되면 WeakMap의 데이토 같이 회수되는 것을 확인할 수 있습니다.

Dom에 추가적인 데이터를 저장할 때 Dom이 삭제되면 데이터가 같이 삭제되기를 원한다면 WeakMap을 사용할 수 있습니다.

Map 메모리 확인해보기

메모리 사용량 뿐 아니라, 개발자 도구의 메모리 탭에서 실제로 변수가 할당되는 것도 확인할 수 있습니다.

예제

예제

예제

카드 UI를 생성해 총 5개의 변수를 할당했습니다. 이제 삭제해보면서 메모리를 확인해보면,

예제

예제

UI 자체는 돔에서 제거되었지만 Map에 강한 참조로 연결되어 있기 때문에, 카드 UI를 삭제했음에도 메모리에 변수가 남아있는것을 볼 수 있습니다.

WeakMap 메모리 확인해보기

먼저 카드 UI를 5개 생성했을 때의 메모리를 보면,

예제

이후 3개 카드 UI를 삭제해보면 그만큼 삭제되어 메모리에는 2개의 값만 남아 있습니다.

예제

반면 WeakMap의 경우에는 약한 참조를 사용하기 때문에, 돔에서 카드 UI가 제거되면 더 이상 메모리에 값이 남아있자 않습니다.

캐시

key로 지정한 객체가 더 이상 참조되지 않게 되면 메모리가 해제 되는 특성으로 캐싱시에 유용합니다.

let cache = new WeakMap(); function computeSumWithCache(obj) { if (!cache.has(obj)) { console.log("계산"); const sum = obj.a + obj.b + obj.c; cache.set(obj, sum); } console.log("캐시 반환"); return cache.get(obj); } let obj = { a: 1, b: 2, c: 3, }; // 계산 // 캐시 반환 let result1 = computeSumWithCache(obj); // 캐시 반환 let result2 = computeSumWithCache(obj); obj = null; // obj가 null 처리되면 GC에 의해 WeakMap에서 해당 엔트리 자동 제거됨

lodash의 memorize 함수에서도 WeakMap을 사용할 수 있습니다.

_.memoize.Cache = WeakMap;

Vue

DOM이 삭제되었을 때 참조를 해제하지 않아도 메모리 누수를 방지할 수 있다는 점에서 Vue 구현에서도 사용된다고 합니다.

Vue에서 반응형 작동 방식 에서는 반응형 객체의 속성 접근을 추적하기 위해 전역 WeakMap을 사용해 객체별 의존성 맵을 관리한다고 설명합니다.

이 WeakMap은 객체를 키로 사용하므로 참조가 끊기면 자동으로 GC 대상이 되어 메모리 누수를 방지할 수 있기 때문에, Proxy로 감지하고 WeakMap으로 추적해 메모리 효율성과 정확한 이펙트 실행이 가능하다고 합니다.

Last updated on