35. 자료구조 - 위크맵

Chipmunk_jeong·2021년 3월 19일
0

TIL

목록 보기
35/62
post-thumbnail

가비지 컬렉션에서 배웠듯이 도달이 불가능한 값은 메모리에서 삭제한다.

let john = { name: "John" };

john = null;
// 객체가 메모리에서 삭제된다.

자료구조를 구성하는 요소도 자신이 속한 자료구조가 메모리에 남아있는 동안 도달 가능한 값으로 취급되어 메모리에 남아있다.
객체의 프로퍼티나 배열의 요소, 맵이나 셋을 구성하는 요소들이 여기에 해당된다.

만약에, 배열에 객체 하나를 추가하고 배열이 메모리에 남아있는 한, 배열의 요소인 이 객체도 메모리에 남아있게 된다.

let user = { name: 'quakka' };
let arr = [user];

user = null; // 참조 대상을 null로 덮어씀

// user을 나타내는 객체는 현재 배열의 요소로 존재하기 때문에
// 객체로써 값에 도달이 불가능하더라도 가비지 컬렉션에 삭제되지 않는다.
console.log(array[0]); // {name: "quakka"}

에서도 객체를 키로 사용한 경우, 이 메모리에 남아 있는 상태에서 키로 존재하는 객체도 메모리에 남아있게 된다.

let user = { name: "quakka" };

let map = new Map();
map.set(user, "It`s Me");

user = null;

for(let key of map.keys()) {
  console.log(key); // {name: "quaka"}
}

console.log(map.size); // 1

하지만 일반적인 이 아닌 위크맵을 사용하면 차이점이 발생한다.
바로 키로 사용한 객체가 가비지컬렉션의 대상이 되기 때문이다.


위크맵

1. 위크맵의 첫 번째 차이는 위크맵의 키가 반드시 객체여야 한다.

즉, 원시데이터는 키로 사용할 수 없다.

let weakMap = new WeakMap();

let user = {name: "quakka"};

weakMap.set(user, "good"); // 정상적으로 할당
weakMap.set('hello', 'world'); // Error

만약에 위크맵의 키로 사용되는 객체를 담은 변수에 새로운 값을 할당하면 객체는 가비지 컬렉션에 의해서 메모리에서 삭제된다.

let map = new WeakMap();

let obj = {};

weakMap.set(obj, 'hi');
weakMap.get(obj); // 'hi'

obj = null;

weakMap.get(obj); // undefined

2. 위크맵은 반복작업과 keys(), values(), entries()등의 메서드를 지원하지 않는다.

위크맵이 지원하는 메서드는 아래와 같다.

  • .get(key)
  • .set(key, value)
  • .delete(key)
  • .has(key)

메서드의 제공량이 적다.
그 이유는 가비지 컬렉션의 동작 방식 때문이다.
가비지 컬렉션의 동작 시점은 정확히 알수 없다.
자바스크립트 엔진이 결정하기 때문이다.
참조되는 객체가 사라졌을 때 즉시 메모리에서 삭제가 될수도 있지만, 다른 작업이 일어날 때까지 대기하다가 같이 삭제될수도 있기 때문이다.
그래서 현재 위크맵에 요소가 몇 개인지 정확히 파악하는 것 자체가 불가능 하다.
즉, 위크맵의 키/값 전체를 대상으로 무언가를 하는 메서드는 동작 자체가 불가능하다.


추가 데이터

그럼 위크맵은 어디세 사용하는 것인가?
위크맵은 부차적인 데이터를 저장할 곳이 필요할 때 사용한다.
외부 코드에 속한 객체를 가지고 작업을 해야 한다면,
이 객체에 데이터를 추가해줘야 하는데 추가 해 줄 데이터는 객체가 살아있는 동안에만 유효해야하는 상황이라고 가정한다.
이럴 때 위크맵이 유용하다.

위크맵에 원하는 데이터를 저장(키는 객체로 저장)하면 객체가 가비지 컬렉션의 대상이 될 때, 데이터도 함께 삭제된다.

weakMap.set(user, "보물지도");
// user가 사라지면 보물지도도 사라진다.

구체적인 예를 살펴보자.

사용자 방문횟수를 세어 주는 코드가 있다.
관련 정보는 맵에 저장하고 있는데 맵 요소의 키엔 특정 사용자를 나타내는 객체를, 값엔 해당 사용자의 방문 횟수를 저장하고 있다.
어떤 사용자가 회원탈퇴를 한다면 해당 사용자의 방문 횟수도 저장할 필요가 사라진다.

let visitUserCount = new Map();

//사용자가 방문시 카운트
function countUser(user) {
  let count = visitUserCount.get(user) || 0;
  visitUserCount.set(user, count + 1);
}

let user = {name: 'quakka'};
countUser(user);

// 회원탈퇴
user = null;

위와 같이 회원을 탈퇴하여 카운트 정보도 필요가 없으면 소멸이 되어야하지만, 맵으로 구현되어있기에 객체가 참조를 안하더라도 맵에서 참조하기에 메모리에 남아있다.
만약 위크맵을 활용한다면 위에서 회원을 탈퇴할 때 그 데이터도 메모리에서 같이 삭제할 수 있다.


캐싱

또한 워크맵캐싱(caching)이 필요할 때 유용하다.
캐싱은 시간이 오래 걸리는 작업의 결과를 저장해서 연산 시간과 비용을 절약해준다.
동일한 함수를 여러번 호출 시, 최초 호출에서 반환된 값을 어딘가에 저장해 놓았다가 그 다음에 함수를 호출하는 대신 저장된 값을 사용하는게 캐싱중의 하나다.

let cache = new Map();

// 연산을 수행하고 그 결과를 맵에 저장합니다.
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;
alert(cache.size); // 1

process를 여러번 호출하면 최초 호출시에만 연산이 일어나고
그 다음부터는 저장되어있는 값이 리턴된다.
하지만 객체가 사라져도 맵이 사용하고 있어 메모리에서 사라지지가 않는다.
워크맵으로 만약 바꾼다면

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;


profile
Web Developer

0개의 댓글