WeakMap

WeakMap은 Map과 유사하지만, Map과 달리 객체만 수집할 수 있고 약한 참조가 이루어져 메모리 누수를 예방할 수 있습니다.

영상 강의

PART 1



예제

기존 코드는 비공개(private) 데이터를 저장하기 위해서 많은 방법을 사용했습니다. 그 중 한가지가 네이밍 컨벤션을 이용한 방법입니다.

function Person(age) {
  this._age = age;
}

Person.prototype._incrementAge = function() {
  this._age += 1;
}

그러나 네이밍 컨벤션은 코드베이스에 대해 혼란을 일으킬 수 있고, 항상 유지된다는 보장을 할 수 없었습니다. ES6+ 부터는 WeakMap을 활용해 비공개 멤버를 관리할 수 있습니다.

let _age = new WeakMap();

class Person {
  constructor(age) {
    _age.set(this, age);
  }
  incrementAge() {
    let age = _age.get(this) + 1;
    _age.set(this, age);
    if (age > 50) {
      console.log('반 백년을 살았구나!~');
    }
  }
}

비공개 데이터를 저장하기 위해 WeakMap을 사용해서 좋은 점은 Reflect.ownKeys()를 사용해도 멤버 이름이 들어나지 않는다는 점입니다.

const person = new Person(50);

person.incrementAge(); // '반 백년을 살았구나!~'

Reflect.ownKeys(person); // []

반면 Symbol을 사용해 비공개 멤버를 만든 경우, Reflect.ownKeys() 사용 시 멤버 이름이 들어납니다.

const _age = Symbol();

class Person {
  constructor(age) {
    this[_age] = age;
  }
  getAge() {
    return this[_age];
  }
}

const person = new Person(52);

Reflect.ownKeys(person); // [Symbol()]

WeakMap을 사용하는 보다 실용적인 사용 예는 DOM 요소 자체를 훼손시키지 않고 DOM 요소에 연관 데이터를 저장하는 것입니다. WeakMap을 참조한 DOM 요소가 DOM에서 제거될 경우 가비지 콜렉션에 의해 제거된 DOM 객체에 약한 참조된 WeakMap이 자동으로 제거됩니다.

let map = new WeakMap();

let someEl = document.querySelector('#some');

// 요소에 대한 약한 참조(weak reference)를 저장
map.set(el, '참조');

// 요소의 값에 접근
let value = map.get(el); // '참조'

// DOM 요소 제거 시, 자동으로 map 참조 제거
el.parentNode.removeChild(el);
el = null;

value = map.get(el); // undefined

앞서 다룬 예제를 통해 알 수 있는 WeakMap의 유용함은 메모리 관리입니다. 예를 들어 jQuery 참조를 가진 DOM 요소 객체의 캐시를 저장하는 방법을 생각해보면, WeakMap을 사용할 경우 DOM에서 제거된 특정 DOM 요소와 관련된 모든 메모리를 자동적으로 절약할 수 있습니다. 전반적으로 WeakMap은 DOM 요소를 감싸는 모든 라이브러리에 매우 유용합니다.

참고