[JS] #16 WeakMap 오브젝트

simoniful·2021년 5월 1일
0

ES6 JS - Basic

목록 보기
16/18
post-thumbnail

개요

  • object만 key로 사용 가능
    String, Number, Symbol 등 다양한 프리미티브 타입의 key로 사용이 가능한 Map과 다르게 object만 key로 사용 가능합니다. value는 제한이 없습니다.

  • Map에서 key로 참조한 object를 삭제하면 object를 사용할 수 없게 되지만 Map에 object가 남습니다.

    let sports = {like: "축구"};  --- (1)
    const obj = new Map([
      [sports, "like:축구"]
    ]);
    sports = {like: "농구"}; --- (2)
  1. let sports = {like: "축구"};
    오브젝트를 sports 변수에 할당
  2. const obj = new Map([[sports, "like:축구"]]);
    Map 인스턴스를 생성하면서 sports를 key로 사용
  3. sports = {like: "농구"};
    새 오브젝트를 만들어서 sports 변수에 할당
    sports 변수가 참조하는 오브젝트의 메모리 주소가 변경
    1번에서 2번으로 주소가 변경, 1번을 나중에 찾을 때 주소가 날라갔으므로 메모리 릭 발생

오브젝트를 삭제 했을 때 Map은 그대로 남아있지만, WeakMap은 Garbage Collection이 개발자가 지우지 않아도 알아서 WeakMap의 오브젝트를 같이 지웁니다. 따라서, 메모리 릭이 발생하지 않습니다.

  • WeakMap 오브젝트 메소드
    • set(), get(), has(), delete()
    • CRUD와 관련된 메소드만 있습니다.
  • WeakMap entry 열거 불가
  • 이터레이션 불가 - next()로 엔트리 읽는 것 불가

new WeakMap()

  • WeakMap 인스턴스 생성, 반환
  • 파라미터 작성
    • 대괄호[] 안에 이터러블 오브젝트 작성
      const empty = new WeakMap();
      const sports = {};
      const obj = new WeakMap([
        [sports, "sports 오브젝트"]
      ]);
      console.log(typeof obj);
      // object

WeakMap 오브젝트 구조

const map = Map;
const weakmap = WeakMap;
const sports = {};
const obj = new WeakMap([
  [sports, "종목"]
]);

  1. map과 weakmap의 scope를 전개해 구조를 살펴보면 크게 다르지는 않습니다.
  2. Map 오브젝트에는 Symbol(Symbol.species)가 있지만 WeakMap 오브젝트에는 없습니다.
    • constructor 오버라이드가 WeakMap에서는 불가능합니다.
  3. Symbol.iterator도 Map 오브젝트에는 있지만 WeakMap에는 없습니다.
    • 이터레이션이 WeakMap에서는 불가능합니다.
  4. 그 밖에 entries, forEach, keys()등 메소드가 WeakMap에는 없습니다.
    • 순차적으로 읽는 것이 불가능합니다.
  5. obj를 scope를 전개해 보면 [[Entries]]가 있습니다
    • 0: {Object => "종목"} 형태로 [sports, "종목"] 형태로 작성한 것을 인덱스를 부여하여 배열로 만들고 엘리먼트에 {Object => "종목"} 형태로 변환합니다. Map 인스턴스와 구조는 같고 안에 들어있는 메소드만 다릅니다.

WeakMap 오브젝트 메소드

4개의 메소드만 존재하며, 모든 메소드가 key를 사용합니다.
key는 반드시 object이며 전체를 열거하거나 반복하는 기능은 없습니다.

get()

  • Map 인스턴스에서 key 값이 같은 value 반환
    • key 값이 같지 않거나 타입이 다르면 undefined 반환
      const fn = () => {};
      const obj = new WeakMap([
        [fn, "함수"]
      ]);
      console.log(obj.get(fn));
      // 함수

set()

  • WeakMap 인스턴스에 key, value 설정
  • 첫 번째 파라미터에 key로 사용할 오브젝트를 작성합니다.
    • primitive value(String, Number, ...)는 사용 불가능합니다.
  • 두 번째 파라미터는 값
    • 첫 번째 파라미터의 오브젝트에 대한 값
    • 오브젝트 구분 등의 용도이며 오브젝트에 따라 연동하는 함수 등록
      const fn = function(){};
      const obj = new WeakMap([
        [fn, "함수"]
      ]);
      console.log(obj.get(fn));
      obj.set(fn, "함수 변경");
      console.log(obj.get(fn));
      // 함수
      // 함수 변경
      ⇒ fn 메모리 주소가 key로 등록되어 있으면 같은 메모리 주소로 값을 설정하므로 [fn, "함수"]에서 "함수"가 "함수 변경"으로 변경됩니다.

has()

  • WeakMap 인스턴스에서 key의 존재여부를 반환하며 존재할 경우 true 아니면 false를 반환합니다.
    const obj = {};
    const weakObj = new WeakMap([
      [obj, "오브젝트"]
    ]);
    console.log(weakObj.has(obj));
    // true

delete()

  • WeakMap 인스턴스에서 key와 일치하는 entry를 삭제합니다.
    • 삭제를 성공하면 true 실패하면 false 반환
      const fn = function(){};
      const obj = new WeakMap([
        [fn, "함수"]
      ]);
      console.log(obj.delete(fn));
      console.log(obj.has(fn));
      // true
      // false

가비지 컬렉션 처리

참조했던 object가 바뀌면 참조했던 오브젝트가 가비지 컬렉션(GC) 처리됩니다.

처리 과정

let obj = new WeakMap();
let sports = () => {point: 1};
obj.set(sports, "변경전");
sports = () => {point: 2};
obj.set(sports, "변경후");
setTimeout(function(){
  console.log(obj.get(sports));
}, 2000);
  1. let sports = () => {point: 1};
    obj.set(sports, "변경전");
    ⇒ sports에 Function 오브젝트를 생성하여 할당하고 이것을 WeakMap 인스턴스에 key로 설정합니다.

  2. sports = () =>{point: 2};

    • 새로운 함수({point:2})를 생성 후 sports에 할당합니다.
    • sports가 참조하는 메모리 주소가 변경됩니다.
    • 메모리 주소가 바뀌면 앞에 sports에서 참조했던 오브젝트를 호출할 수 없게 됩니다.
    • 이렇게 사용할 수 없게 된 {point: 1} 오브젝트는 GC 대상이 됩니다.
    • 엔진이 주기적으로 GC 처리를 합니다.
  3. obj.set(sports, "변경후");

    • sports를 key로 하여 WeakMap에 설정합니다.
    • 앞에서 sports를 key로 설정했기에 여기서 sports를 key로 하여 설정하면 값이 대체되야하지만
    • 두 sports가 참조하는 메모리주소가 각각 다르기 때문에 sports가 추가됩니다. 2개가 WeakMap 인스턴스에 설정됩니다.

    구조분석

    1. sports가 {point:2}로 새로운 오브젝트를 할당해서 sports가 참조하는 오브젝트가 바뀝니다.
    2. 변경 후 obj의 [[Entries]]를 펼쳐보면 0과 1 인덱스에 두 개의 형태가 등록되어있습니다. 변수값은 바뀌어도 하나지만 WeakMap 인스턴스에는 두 개가 있습니다.
    3. {point: 1}과 {point: 2}의 메모리 주소가 다르며
      sports 변수는 사람이 보는 것으로 하나이지만, WeakMap은 값인 메모리 주소가 다르므로 각각 저장합니다.
    4. 그렇기에 sports로 저장하는게 아닌 인덱스를 부여하여 저장하는 것입니다.
    5. 엔진내부에서는 인덱스가 key이며 sports는 프로퍼티 value에서 프로퍼티 키가 됩니다.
    6. {point: 1}의 sports를 사용할 수 없으므로 GC가 {point: 1}의 sports를 메모리에서 지웁니다. 또한, obj의 "변경전"도 삭제합니다.
    7. 인덱스 1번이 0번이 됩니다.

Map과 WeakMap의 차이

  • 참조하는 object를 삭제하면 Map은 그대로 갖고 있지만 WeakMap은 GC처리로 삭제됩니다.
  • Map은 열거하거나 반복하는 처리가 가능하지만 WeakMap은 불가능합니다.
  • GC 처리로 인해 엔트리가 유동적으로 변하기 때문에 WeakMap의 키로 설정된 object를 설정, 읽음, 삭제만 가능합니다.
  • 따라서, 삭제나 변경될 가능성이 있는 경우에는 WeakMap을 사용해야합니다.
let mapObj = new Map();
(function(){
  const obj = {key: "value"};
  mapObj.set(obj, "Map");
}()); // ----- (1)
let weakObj = new WeakMap();
(function(){
  const obj = {key: "value"};
  weakObj.set(obj, "WeakMap");
}()); // ----- (2)
setTimeout(function() {
  console.log(weakObj);
  console.log(mapObj);
}, 1000);

즉시 실행함수는 일회용이기에 변수를 저장하지 않을 때 사용합니다. 그렇기에 함수가 끝나면 obj변수는 GC가 메모리에서 지웁니다.

(1): Map은 obj 변수가 함수 종료후 GC에 의해 지워지더라도 Map에 설정된 obj를 지우지 않고 유지합니다. 엔트리가 남아있습니다. 변수가 삭제되는 상황처럼 불일치가 발생할 때는 사용이 제한됩니다.

(2): WeakMap은 앞의 (1)과 동일하지만 Map이 아닌 WeakMap에 저장합니다. WeakMap은 obj 변수가 삭제되면 WeakMap에 설정된 obj를 삭제합니다. 엔트리가 없어집니다.

구조 분석

  1. setTimeout(function(){...});
    GC가 수거대상을 수거하기 위해 즉시 실행되는게 아니기 때문에 setTimeout()으로 3초뒤 출력하도록 setTimeout()함수를 사용합니다.
  2. 전개구조를 보면 mapObj 인스턴스에는 key로 {key:"value"}가 등록되어 있습니다. 실제로 GC에 의해 obj는 수거되었지만 말이죠. 이는 메모리 릭(memory leak)을 유발하여 크리티컬할 수 있습니다.
  3. weakObj의 [[Entries]]를 전개해보면 No properties입니다. GC에서 obj({key: "value})를 수거할 때 weakMap의 해당 key도 지우기 때문입니다.

정리

Map은 기본적인 CRUD 메소드 뿐아니라 forEach, keys, values 등 반복 순회하는 이터레이션 메소드들 또한 연결되어있습니다.

Map의 경우 GC에 의해 key가 수거되거나 하지 않습니다. 그렇기 때문에 엔트리 사이즈가 정적이고 명시적으로만 변경할 수 있습니다

하지만, WeakMap은 CRUD 메소드만 연결되어있고 Key를 통해서만 접근할 수 있으며 열거할 수 없습니다.

WeakMap은 GC에 의해 엔트리가 유동적으로 변경될 수 있기 때문에 열거나 나열할 수 없습니다.

profile
소신있게 정진합니다.

0개의 댓글