반응형
Jake Seo
제이크서 위키 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (715)
    • 일상, 일기 (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 자바스크립트 인터뷰
  • 자바
  • 팩터리 메서드 패턴
  • 러스트
  • 느린 쿼리
  • 자료구조
  • 이펙티브 자바 item9
  • 작업기억공간
  • 자바 디자인패턴
  • 스프링 검증
  • 자바 검증
  • 객체복사
  • 프로그래머의 뇌
  • 서버리스 컴퓨팅
  • next js app
  • 추상 팩터리 패턴
  • 이펙티브 자바
  • 외래키 제약조건
  • NEXT JS
  • Next.js
  • item8
  • 자바스크립트
  • Pre-rendering
  • 싱글톤 패턴
  • 빈 검증
  • 플라이웨이트패턴
  • 메이븐 페이즈
  • 메이븐 라이프사이클
  • try-with-resources
  • item9
  • 토비의 스프링
  • pnpm
  • prerendering
  • 참조 해제
  • rust
  • 자바스크립트 면접
  • 싱글턴
  • MSSQL
  • 메이븐 골
  • 알고리즘
  • 싱글톤
  • Javadoc 자바독 자바주석 주석 Comment
  • 도커공식문서
  • Java
  • 이펙티브자바
  • 슬로우 쿼리
  • bean Validation
  • 디자인패턴
  • item7
  • serverless computing

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 위키 블로그

모던 자바스크립트, 위크 맵(WeakMap) 과 위크 셋(WeakSet)
자바스크립트/모던 자바스크립트

모던 자바스크립트, 위크 맵(WeakMap) 과 위크 셋(WeakSet)

2023. 3. 24. 00:58

위크맵 (WeakMap)

  • 키를 메모리에 유지하지 않고 키와 관련된 값을 저장할 수 있다.
  • 만일 키를 참조하는 것이 사라지면, 가비지 컬렉트 때 해당 키가 사라진다.

위크맵은 Iterable 이 아니다.

  • Map 은 이터러블이었지만, WeakMap 은 아니다.
  • 이러한 특성 때문에 키를 알아야만 값을 가져올 수 있다.
  • Iterator 를 제공하면 제공된 Iterator 에 의해 키가 계속 참조되고, 영원히 가비지 컬렉트 될 수 없기 때문이다.

위크맵이 제공하는 메서드

  • has(): 키의 존재 여부 반환
  • get(): 키에 대한 값 반환, 없다면 undefined
  • delete(): 키 삭제 성공하면 true 실패하면 false

WeakMap 은 약한 참조를 이용하는 만큼, 키를 모르면 참조할 수 없기 때문에 키에 접근하는 size, forEach,keys, values 등이 없다.

WeakMap 은 이름이 비슷하고 인터페이스가 비슷하지만 Map 의 서브 클래스가 아니다.

위크맵의 용례

  • 어떤 경우에 위크맵을 쓰면 좋을까?

private 한 정보를 보관할 때

  • 아래 예제에서 private 한 정보를 보관하는 Map 으로 사용되었다.
  • ES2021 에서는 Private class feature 가 나와서 사실 이것보다 훨씬 간단한 문법으로 이를 수행할 수 있다.
const Example = (() => {
  const privateMap = new WeakMap();

  return class Example {
    constructor() {
      privateMap.set(this, 0);
    }

    incrementCounter() {
      const result = privateMap.get(this) + 1;
      privateMap.set(this, result);
      return result;
    }

    showCounter() {
      console.log(`Counter is ${privateMap.get(this)}`);
    }
  };
})();

const e1 = new Example(); // Example {}
e1.incrementCounter();
console.log(e1); // Example {}

const e2 = new Example();
e2.incrementCounter();
e2.incrementCounter();
e2.incrementCounter();

e1.showCounter(); // Counter is 1
e2.showCounter(); // Counter is 3

사실 Map 을 사용해도 비공개라는 속성 자체는 유지된다. 그러나, 계속 맵에 데이터가 쌓이고 쌓일수록 가비지 컬렉트 되지 않고 맵의 덩치가 커질 것이다.

제어할 수 없는 객체에 대한 정보 저장

  • 이를테면, 브라우저의 DOM 객체를 키로 걸고 그에 대한 값을 저장해둘 수 있다.
  • 이 DOM 객체는 사용자의 인터렉션을 통해 지워질 수 있는데, 지워지면 WeakMap 키의 레퍼런스도 날아가기 때문에 추후에 가비지컬렉트된다.
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Storing Data for DOM Elements</title>
  </head>
  <style>
  .person {
      cursor: pointer;
  }
  </style>
  <body>
    <label>
    <div id="status"></div>
    <div id="people"></div>
    <div id="person"></div>
    <script src="storing-data-for-dom.js"></script>
  </body>
</html>
(async () => {
  const statusDisplay = document.getElementById("status");
  const personDisplay = document.getElementById("person");
  try {
    // The WeakMap that will hold the information related to our DOM elements
    const personMap = new WeakMap();
    await init();

    async function init() {
      const peopleList = document.getElementById("people");
      const people = await getPeople();
      // In this loop, we store the person that relates to each div in the
      // WeakMap using the div as the key
      for (const person of people) {
        const personDiv = createPersonElement(person);
        personMap.set(personDiv, person);
        peopleList.appendChild(personDiv);
      }
    }

    async function getPeople() {
      // This is a stand-in for an operation that would fetch the person
      // data from the server or similar
      return [
        { name: "Joe Bloggs", position: "Front-End Developer" },
        { name: "Abha Patel", position: "Senior Software Architect" },
        { name: "Guo Wong", position: "Database Analyst" },
      ];
    }

    function createPersonElement(person) {
      const div = document.createElement("div");
      div.className = "person";
      div.innerHTML =
        '<a href="#show" class="remove">X</a> <span class="name"></span>';
      div.querySelector("span").textContent = person.name;
      div.querySelector("a").addEventListener("click", removePerson);
      div.addEventListener("click", showPerson);
      return div;
    }

    function stopEvent(e) {
      e.preventDefault();
      e.stopPropagation();
    }

    function showPerson(e) {
      stopEvent(e);
      // Here, we get the person to show by looking up the clicked element
      // in the WeakMap
      const person = personMap.get(this);
      if (person) {
        const { name, position } = person;
        personDisplay.textContent = `${name}'s position is: ${position}`;
      }
    }

    function removePerson(e) {
      stopEvent(e);
      this.closest("div").remove();
    }
  } catch (error) {
    statusDisplay.innerHTML = `Error: ${error.message}`;
  }
})();

키를 참조하는 값이 있다면 메모리 해제가 가능할까?

  • 구현 스펙에 키로 사용되는 객체가 값에서 시작되는 경로로만 참조가 가능하다면, 가비지컬렉트 된다고 나와있다.
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Values Referring Back to the Key</title>
  </head>
  <body>
    <label>
      Objects to create:
      <input type="text" id="objects" value="100000" />
    </label>
    <input type="button" id="btn-create" value="Create" />
    <input type="button" id="btn-release" value="Release" />
    <script src="value-referring-to-key.js"></script>
  </body>
</html>
const log = (msg) => {
  const p = document.createElement("pre");
  p.appendChild(document.createTextNode(msg));
  document.body.appendChild(p);
};

const AAAAExample = (() => {
  const privateMap = new WeakMap();

  return class AAAAExample {
    constructor(secret, limit) {
      privateMap.set(this, { counter: 0, owner: this });
    }

    get counter() {
      return privateMap.get(this).counter;
    }

    incrementCounter() {
      return ++privateMap.get(this).counter;
    }
  };
})();

const e = new AAAAExample();

document.getElementById("btn-create").addEventListener("click", function (e) {
  const counter = +document.getElementById("objects").value || 100000;
  log(`Generating ${count} objects...`);

  for (let n = count; n > 0; n--) {
    a.push(new AAAAExample());
  }

  log(`Done, ${a.length} objects in the array`);
});

document.getElementById("btn-release").addEventListener("click", function (e) {
  a.length = 0;
  log("All objects released");
});
  • owner 에서 this 를 이용해 자기 자신을 참조하고 있다.
  • 동작은 아주 간단하다.
    • Create 버튼을 누르면 AAAAExample 클래스를 10만개 만든다.
      • 만들어진 10만개의 AAAAExample 을 배열 a 에서 참조하도록 만든다.
    • Release 버튼을 누르면 배열 a 의 length 를 0 으로 만들어서 참조를 끊는다.

  • Create 버튼을 눌렀을 때의 스냅샷이다.

  • Release 버튼을 눌렀을 때의 스냅샷이다.

  • Collect garbage 버튼을 누르면 더 빠르게 가비지 컬렉트를 시킬 수 있다.

위크셋, 위크세트 (WeakSet)

  • WeakMap 의 Set 버전이다.
  • WeakMap 과 대부분의 특성이 동일하다.
  • 다만, 엔트리를 저장하는 것이 아니라 값을 저장한다.

용례

  • 이전에 이 객체를 보았는지 확인할 때 유용하다.

일회성 토큰 예제

  • 단 한번만 사용되어야 하는 일회성 토큰을 저장하는 경우, 해당 토큰이 이미 사용되었는지 판단해주는데에 유용하다.
  • 만일 해당 토큰의 유효기간이 끝났다면 참조가 해제될테고 가비지컬렉트 될 것이다.
    • 이 경우 다시 해당 토큰을 사용할 수 있게 되는 원리다.
const SingleUseObject = (() => {
  const used = new WeakSet();

  return class SingleUseObject {
    constructor(name) {
      this.name = name;
    }

    use() {
      if (used.has(this)) {
        throw new Error(`${this.name} has already been used`);
      }

      console.log(`Using ${this.name}`);
      used.add(this);
    }
  };
})();

const obj1 = new SingleUseObject("hello");
const obj2 = new SingleUseObject("what");

obj1.use();
obj1.use();

출처 확인 예제

  • 이 객체의 출처가 정확히 어느곳인지 확인하는데 사용할 수 있다.
  • 물론 이 경우에도 해당 값에 대한 참조가 끊기면 자동으로 가비지 컬렉트 될 것이다.
const Thingy = (() => {
  const known = new WeakSet();
  let nextId = 1;

  return class Thingy {
    constructor(name) {
      this.name = name;
      this.id = nextId++;
      Object.freeze(this);
      known.add(this);
    }

    action() {
      if (!known.has(this)) {
        throw new Error("정상적인 방법으로 만들어진 Thingy 가 아닙니다.");
      }

      console.log(`Action on Thingy #${this.id} (${this.name})`);
    }
  };
})();

const t1 = new Thingy("t1");
t1.action();
const t2 = new Thingy("t2");
t2.action();

const faket2 = Object.create(Thingy.prototype);
faket2.name = "faket2";
faket2.id = 2;
Object.freeze(faket2);
faket2.action(); // 오직 생성자로 만든 Thingy 만 사용 가능.
반응형
저작자표시 비영리 (새창열림)

'자바스크립트 > 모던 자바스크립트' 카테고리의 다른 글

모던 자바스크립트, 모듈 2 - 모듈의 동작 방식  (0) 2023.03.26
모던 자바스크립트, 모듈 1 - import 와 export 방식  (0) 2023.03.26
모던 자바스크립트, 셋 혹은 세트 (Set)  (0) 2023.03.23
모던 자바스크립트, 맵 (Map)  (0) 2023.03.23
모던 자바스크립트, 4가지 동등성 비교 알고리즘  (0) 2023.03.22
    '자바스크립트/모던 자바스크립트' 카테고리의 다른 글
    • 모던 자바스크립트, 모듈 2 - 모듈의 동작 방식
    • 모던 자바스크립트, 모듈 1 - import 와 export 방식
    • 모던 자바스크립트, 셋 혹은 세트 (Set)
    • 모던 자바스크립트, 맵 (Map)
    Jake Seo
    Jake Seo
    ✔ 잘 보셨다면 광고 한번 클릭해주시면 큰 힘이 됩니다. ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바