[javascript] ES6 WeakMap의 실제 용도는 무엇입니까?

WeakMapECMAScript 6에 도입 된 데이터 구조 의 실제 용도는 무엇입니까 ?

약한 맵의 키가 약한 맵에 삽입 된 값 것을 보장 대응하는 값에 강한 기준을 작성하기 때문에 않을 만큼의 키가 아직 살아로서 사라질,이 메모 테이블을 사용할 수없는, 캐시 또는 일반적으로 약한 참조, 약한 값을 가진 맵 등을 사용하는 것.

이것은 나에게 보인다 :

weakmap.set(key, value);

… 이것에 대한 원형 교차로입니다.

key.value = value;

어떤 구체적인 사용 사례가 누락 되었습니까?



답변

근본적으로

WeakMaps는 가비지 수집을 방해하지 않고 외부에서 객체를 확장하는 방법을 제공합니다. 개체를 확장하려고하지만 봉인되어 있거나 외부 소스에서 개체를 확장 할 수 없을 때마다 WeakMap을 적용 할 수 있습니다.

WeakMap은지도 (사전)이다 키가 받는 모든 참조 경우입니다 – 약한 키가 손실되고 값의 참조가없는 – 값이 쓰레기 수집 할 수 있습니다가. 먼저 예제를 통해 이것을 보여주고 약간 설명하고 마침내 실제 사용으로 마무리합시다.

특정 객체를 제공하는 API를 사용한다고 가정 해 보겠습니다.

var obj = getObjectFromLibrary();

이제 객체를 사용하는 메소드가 있습니다.

function useObj(obj){
   doSomethingWith(obj);
}

특정 객체로 메소드가 몇 번이나 호출되었는지 추적하고 N 번 이상 발생하면보고하고 싶습니다. 순진하게도 맵을 사용하려고 생각합니다.

var map = new Map(); // maps can have object keys
function useObj(obj){
    doSomethingWith(obj);
    var called = map.get(obj) || 0;
    called++; // called one more time
    if(called > 10) report(); // Report called more than 10 times
    map.set(obj, called);
}

이것은 작동하지만 메모리 누수가 있습니다. 이제 라이브러리 객체가 가비지 수집되지 않도록하는 함수에 전달 된 모든 단일 라이브러리 객체를 추적합니다. 대신에-를 사용할 수 있습니다 WeakMap:

var map = new WeakMap(); // create a weak map
function useObj(obj){
    doSomethingWith(obj);
    var called = map.get(obj) || 0;
    called++; // called one more time
    if(called > 10) report(); // Report called more than 10 times
    map.set(obj, called);
}

그리고 메모리 누수가 사라졌습니다.

사용 사례

그렇지 않으면 메모리 누수를 유발하고에 의해 활성화되는 일부 사용 사례 WeakMap는 다음과 같습니다.

  • 특정 개체에 대한 개인 정보를 유지하고지도를 참조하여 사람들에게만 액세스 할 수 있습니다. 개인 기호 제안과 함께 좀 더 임시 접근 방식이 제공되지만 지금부터 오랜 시간이 걸렸습니다.
  • 라이브러리 객체를 변경하거나 오버 헤드를 발생시키지 않고 라이브러리 객체에 대한 데이터를 유지합니다.
  • 숨겨진 유형의 클래스에 문제가 발생하지 않도록 유형의 많은 오브젝트가있는 작은 오브젝트 세트에 대한 데이터 유지 JS 엔진은 동일한 유형의 오브젝트에 사용합니다.
  • 브라우저에서 DOM 노드와 같은 호스트 객체에 대한 데이터 유지
  • 외부에서 객체에 기능 추가 (다른 답변의 이벤트 이미 터 예제와 같이).

실제 사용을 보자

외부에서 물체를 확장하는 데 사용할 수 있습니다. Node.js의 실제 세계에서 실제적인 (적응 된 일종의 실제-포인트를 만들기 위해) 예를 들어 봅시다.

Node.js이고 Promise객체 가 있다고 가정 해 봅시다. 이제는 현재 거부 된 모든 약속을 추적하려고하지만 참조가없는 경우 가비지 수집을 피하고 싶지 는 않습니다 .

지금, 당신은 하지 않는 분명한 이유 네이티브 객체에 속성을 추가 할 – 당신이 붙어있어 수 있도록. 약속을 참조하면 가비지 수집이 발생할 수 없으므로 메모리 누수가 발생합니다. 참조를 유지하지 않으면 개별 약속에 대한 추가 정보를 저장할 수 없습니다. 약속의 ID 저장과 관련된 모든 체계는 본질적으로 약속의 참조가 필요함을 의미합니다.

약한지도 입력

WeakMaps는 가 약하다는 것을 의미합니다 . 약한 맵을 열거하거나 모든 값을 얻는 방법은 없습니다. 약한 맵에서 키를 기반으로 데이터를 저장할 수 있으며 키가 가비지 수집되는 시점도 값을 저장합니다.

이것은 약속이 주어지면 그것에 대한 상태를 저장할 수 있으며 객체는 여전히 가비지 수집 될 수 있음을 의미합니다. 나중에 개체에 대한 참조가 있으면 관련된 상태가 있는지 확인하고보고 할 수 있습니다.

이것은 Petka Antonov의 처리되지 않은 거부 후크다음 과 같이 구현하는 데 사용되었습니다 .

process.on('unhandledRejection', function(reason, p) {
    console.log("Unhandled Rejection at: Promise ", p, " reason: ", reason);
    // application specific logging, throwing an error, or other logic here
});

약속에 대한 정보를지도에 보관하고 거부 된 약속이 처리 된시기를 알 수 있습니다.


답변

이 답변은 실제 시나리오에서 편향되어 사용할 수없는 것으로 보입니다. 그대로 읽어보고 실험 이외의 다른 옵션으로는 고려하지 마십시오.

유스 케이스는 청취자를위한 사전으로 사용하는 것일 수 있습니다. 동료가 있습니다. 모든 청취자가 이러한 방식으로 직접 타겟팅되므로 매우 유용합니다. 안녕 listener.on.

그러나보다 추상적 인 관점에서 볼 WeakMap때 기본적으로 모든 것에 대한 액세스를 비 물질화하는 것이 특히 강력합니다.이 구조의 특성에 의해 이미 암시되어 있으므로 멤버를 분리하기 위해 네임 스페이스가 필요하지 않습니다. 어색한 중복 객체 키를 교체하여 메모리를 크게 개선 할 수 있다고 확신합니다 (해체가 당신을 위해 일하더라도).


다음 내용을 읽기 전에

나는 지금 내 정확하게 문제와 같이 해결하는 가장 좋은 방법은 아닙니다 강조 실현 않는 벤자민 Gruenbaum는 ,이 문제가 정기적으로 해결 될 수 없었다 (P가 내 위에 이미 아니라면, 그의 대답을 체크 아웃) 지적을 Map하기 때문에, 유출되었을 것이므로 주된 장점은 WeakMap참조를 유지하지 않으면 가비지 수집을 방해하지 않는다는 것입니다.


여기 내 동료의 실제 코드는 (덕분에 공유)

여기 에 대한 전체 소스 는 위에서 설명한 청취자 관리에 관한 것입니다 ( 사양을 볼 수도 있습니다 )

var listenableMap = new WeakMap();


export function getListenable (object) {
    if (!listenableMap.has(object)) {
        listenableMap.set(object, {});
    }

    return listenableMap.get(object);
}


export function getListeners (object, identifier) {
    var listenable = getListenable(object);
    listenable[identifier] = listenable[identifier] || [];

    return listenable[identifier];
}


export function on (object, identifier, listener) {
    var listeners = getListeners(object, identifier);

    listeners.push(listener);
}


export function removeListener (object, identifier, listener) {
    var listeners = getListeners(object, identifier);

    var index = listeners.indexOf(listener);
    if(index !== -1) {
        listeners.splice(index, 1);
    }
}


export function emit (object, identifier, ...args) {
    var listeners = getListeners(object, identifier);

    for (var listener of listeners) {
        listener.apply(object, args);
    }
}


답변

WeakMap 캡슐화 및 정보 숨기기에 적합

WeakMapES6 이상에서만 사용할 수 있습니다. A WeakMap는 키가 객체 여야하는 키 및 값 쌍의 모음입니다. 다음 예제에서는 WeakMap두 가지 항목으로를 만듭니다 .

var map = new WeakMap();
var pavloHero = {first: "Pavlo", last: "Hero"};
var gabrielFranco = {first: "Gabriel", last: "Franco"};
map.set(pavloHero, "This is Hero");
map.set(gabrielFranco, "This is Franco");
console.log(map.get(pavloHero));//This is Hero

우리는이 set()메소드를 사용하여 객체와 다른 아이템 (우리의 경우 문자열) 사이의 연관을 정의했습니다. 이 get()방법을 사용하여 객체와 관련된 항목을 검색했습니다. 의 흥미로운 측면은 WeakMap맵 내부의 키에 대한 약한 참조를 가지고 있다는 사실입니다. 약한 참조는 객체가 파괴되면 가비지 수집기가에서 전체 항목을 제거하여 WeakMap메모리를 비 웁니다.

var TheatreSeats = (function() {
  var priv = new WeakMap();
  var _ = function(instance) {
    return priv.get(instance);
  };

  return (function() {
      function TheatreSeatsConstructor() {
        var privateMembers = {
          seats: []
        };
        priv.set(this, privateMembers);
        this.maxSize = 10;
      }
      TheatreSeatsConstructor.prototype.placePerson = function(person) {
        _(this).seats.push(person);
      };
      TheatreSeatsConstructor.prototype.countOccupiedSeats = function() {
        return _(this).seats.length;
      };
      TheatreSeatsConstructor.prototype.isSoldOut = function() {
        return _(this).seats.length >= this.maxSize;
      };
      TheatreSeatsConstructor.prototype.countFreeSeats = function() {
        return this.maxSize - _(this).seats.length;
      };
      return TheatreSeatsConstructor;
    }());
})()


답변

????????

약한지도를 사용하면 가비지 수집을 방해하거나 동료를 화나게하지 않고도 DOM 요소에 대한 메타 데이터를 저장할 수 있습니다. 예를 들어, 웹 페이지의 모든 요소를 ​​숫자로 색인화하는 데 사용할 수 있습니다.

??????? ???????? ?? ???????? :

var elements = document.getElementsByTagName('*'),
  i = -1, len = elements.length;

while (++i !== len) {
  // Production code written this poorly makes me want to cry:
  elements[i].lookupindex = i;
  elements[i].elementref = [];
  elements[i].elementref.push( elements[(i * i) % len] );
}

// Then, you can access the lookupindex's
// For those of you new to javascirpt, I hope the comments below help explain 
// how the ternary operator (?:) works like an inline if-statement
document.write(document.body.lookupindex + '<br />' + (
    (document.body.elementref.indexOf(document.currentScript) !== -1)
    ? // if(document.body.elementref.indexOf(document.currentScript) !== -1){
    "true"
    : // } else {
    "false"
  )   // }
);

????? ???????? ??? ???????? :

var DOMref = new WeakMap(),
  __DOMref_value = Array,
  __DOMref_lookupindex = 0,
  __DOMref_otherelement = 1,
  elements = document.getElementsByTagName('*'),
  i = -1, len = elements.length, cur;

while (++i !== len) {
  // Production code written this greatly makes me want to ?:
  cur = DOMref.get(elements[i]);
  if (cur === undefined)
    DOMref.set(elements[i], cur = new __DOMref_value)

  cur[__DOMref_lookupindex] = i;
  cur[__DOMref_otherelement] = new WeakSet();
  cur[__DOMref_otherelement].add( elements[(i * i) % len] );
}

// Then, you can access the lookupindex's
cur = DOMref.get(document.body)
document.write(cur[__DOMref_lookupindex] + '<br />' + (
    cur[__DOMref_otherelement].has(document.currentScript)
    ? // if(cur[__DOMref_otherelement].has(document.currentScript)){
    "true"
    : // } else {
    "false"
  )   // }
);

??? ??????????

weakmap 버전이 더 길다는 점을 제외하고는 차이가 무시할 만 할 수 있지만 위에 표시된 두 코드 조각 간에는 큰 차이가 있습니다. 약한 맵이없는 첫 번째 코드 스 니펫에서 코드 조각은 DOM 요소 사이의 모든 방식으로 참조를 저장합니다. 이렇게하면 DOM 요소가 가비지 수집되지 않습니다.(i * i) % len아무도 사용하지 않는 이상한 것처럼 보일지 모르지만 다시 생각하십시오. 많은 프로덕션 코드에는 문서 전체에 튀는 DOM 참조가 있습니다. 이제 두 번째 코드에서는 요소에 대한 모든 참조가 약하기 때문에 노드를 제거하면 브라우저에서 해당 노드가 사용되지 않는지 (코드로 도달 할 수 없음) 확인할 수 있습니다. 따라서 메모리에서 삭제하십시오. 메모리 사용과 메모리 앵커 (사용되지 않은 요소가 메모리에 유지되는 첫 번째 코드 스 니펫과 같은 것)에 대해 걱정해야하는 이유는 더 많은 메모리 사용이 더 많은 브라우저 GC 시도를 의미하기 때문에 (메모리를 확보하려고 시도하는 것) 브라우저 충돌 방지)는 브라우징 경험이 느려지고 때로는 브라우저 충돌이 발생 함을 의미합니다.

이것들에 대한 polyfill에 관해서는, 나는 내 자신의 라이브러리를 추천 할 것입니다 ( 여기에서 @ github ). 이 라이브러리는 다른 폴리 필에서 찾을 수있는 복잡한 프레임 워크없이 간단하게 폴리 필 할 수있는 매우 가벼운 라이브러리입니다.

~ 행복한 코딩!


답변

나는 사용한다 WeakMap 불변의 객체를 매개 변수로 취하는 함수를 걱정없이 메모하는 캐시에 합니다.

메모는 “값을 계산 한 후 캐시하여 다시 계산할 필요가 없다”고 말하는 멋진 방법입니다.

예를 들면 다음과 같습니다.

몇 가지 참고할 사항 :

  • Immutable.js 객체는 사용자가 객체를 수정할 때 새 포인터를 사용하여 새 객체를 반환하므로 WeakMap에서 키로 사용하면 동일한 계산 값이 보장됩니다.
  • WeakMap은 (키로 사용 된) 객체가 가비지 수집되면 WeakMap에서 계산 된 값도 가져 오기 때문에 메모 에 유용합니다.

답변

WeakMaps에 대한 간단한 기능 기반 사용 사례 / 예가 있습니다.

사용자 모음 관리

나는 진형 User그 속성 포함 개체 fullname, username, age, gender전화를하는 방법 print다른 속성의 사람이 읽을 수있는 요약을 인쇄합니다.

/**
Basic User Object with common properties.
*/
function User(username, fullname, age, gender) {
    this.username = username;
    this.fullname = fullname;
    this.age = age;
    this.gender = gender;
    this.print = () => console.log(`${this.fullname} is a ${age} year old ${gender}`);
}

그런 다음에 users의해 키가 지정된 여러 사용자의 모음을 유지하기 위해 호출 된 맵을 추가 했습니다 username.

/**
Collection of Users, keyed by username.
*/
var users = new Map();

또한 Collection을 추가하려면 사용자를 추가, 가져 오기, 삭제하기위한 도우미 기능과 완전성을 위해 모든 사용자를 인쇄하는 기능도 필요했습니다.

/**
Creates an User Object and adds it to the users Collection.
*/
var addUser = (username, fullname, age, gender) => {
    let an_user = new User(username, fullname, age, gender);
    users.set(username, an_user);
}

/**
Returns an User Object associated with the given username in the Collection.
*/
var getUser = (username) => {
    return users.get(username);
}

/**
Deletes an User Object associated with the given username in the Collection.
*/
var deleteUser = (username) => {
    users.delete(username);
}

/**
Prints summary of all the User Objects in the Collection.
*/
var printUsers = () => {
    users.forEach((user) => {
        user.print();
    });
}

NodeJS 와 같은 위의 모든 코드를 실행 하면 users맵 만 전체 프로세스 내에서 사용자 객체에 대한 참조를 갖습니다. 개별 사용자 개체에 대한 다른 참조는 없습니다.

이 코드를 대화식 NodeJS 쉘로 실행하는 예를 들어 네 명의 사용자를 추가하고 인쇄합니다.
사용자 추가 및 인쇄

기존 코드를 수정하지 않고 사용자에게 더 많은 정보 추가

이제 각 사용자 SMP (소셜 미디어 플랫폼) 링크를 사용자 개체와 함께 추적해야하는 새로운 기능이 필요하다고 가정합니다.

여기서 핵심은이 기능이 기존 코드에 대한 최소한의 개입으로 구현되어야한다는 것입니다.

이는 다음과 같은 방식으로 WeakMaps에서 가능합니다.

Twitter, Facebook, LinkedIn에 대해 별도의 WeakMaps 3 개를 추가합니다.

/*
WeakMaps for Social Media Platforms (SMPs).
Could be replaced by a single Map which can grow
dynamically based on different SMP names . . . anyway...
*/
var sm_platform_twitter = new WeakMap();
var sm_platform_facebook = new WeakMap();
var sm_platform_linkedin = new WeakMap();

getSMPWeakMap주어진 SMP 이름과 관련된 WeakMap을 반환하기 위해 도우미 함수 가 추가되었습니다.

/**
Returns the WeakMap for the given SMP.
*/
var getSMPWeakMap = (sm_platform) => {
    if(sm_platform == "Twitter") {
        return sm_platform_twitter;
    }
    else if(sm_platform == "Facebook") {
        return sm_platform_facebook;
    }
    else if(sm_platform == "LinkedIn") {
        return sm_platform_linkedin;
    }
    return undefined;
}

지정된 SMP WeakMap에 사용자 SMP 링크를 추가하는 기능입니다.

/**
Adds a SMP link associated with a given User. The User must be already added to the Collection.
*/
var addUserSocialMediaLink = (username, sm_platform, sm_link) => {
    let user = getUser(username);
    let sm_platform_weakmap = getSMPWeakMap(sm_platform);
    if(user && sm_platform_weakmap) {
        sm_platform_weakmap.set(user, sm_link);
    }
}

지정된 SMP에있는 사용자 만 인쇄하는 기능입니다.

/**
Prints the User's fullname and corresponding SMP link of only those Users which are on the given SMP.
*/
var printSMPUsers = (sm_platform) => {
    let sm_platform_weakmap = getSMPWeakMap(sm_platform);
    console.log(`Users of ${sm_platform}:`)
    users.forEach((user)=>{
        if(sm_platform_weakmap.has(user)) {
            console.log(`\t${user.fullname} : ${sm_platform_weakmap.get(user)}`)
        }
    });
}

이제 각 사용자가 여러 SMP에 대한 링크를 가질 수 있도록 사용자에 대한 SMP 링크를 추가 할 수 있습니다.

… 이전 예제에서 계속해서 사용자에게 SMP 링크, 사용자 Bill 및 Sarah에 대한 여러 링크를 추가 한 다음 각 SMP에 대한 링크를 별도로 인쇄합니다.
사용자에게 SMP 링크 추가 및 표시

이제 users를 호출 하여 지도 에서 사용자가 삭제되었다고 가정합니다 deleteUser. 사용자 개체에 대한 유일한 참조가 제거됩니다. 또한 사용자 오브젝트가 없으면 SMP 링크에 액세스 할 수있는 방법이 없기 때문에 가비지 콜렉션에 의한 모든 SMP WeakMaps에서 SMP 링크를 지 웁니다.

… 예제를 계속 진행하면서 사용자 Bill을 삭제 하고 연관된 SMP 링크를 인쇄합니다.

맵에서 사용자 Bill을 삭제하면 SMP 링크도 제거됩니다.

SMP 링크를 개별적으로 삭제하기위한 추가 코드와이 기능이 수정되지 않은 기존 코드는 별도로 필요하지 않습니다.

WeakMaps를 사용하거나 사용하지 않고이 기능을 추가하는 다른 방법이 있으면 언제든지 의견을 말하십시오.


답변