web-dev-qa-db-ja.com

ES6 WeakMapの実際の用途は何ですか?

ECMAScript 6で導入されたWeakMapデータ構造の実際の用途は何ですか?

ウィークマップのキーは対応する値への強い参照を作成するので、ウィークマップに挿入された値がそのキーである限りneverが消えることを保証しますそれはまだ生きている、それはあなたが通常弱い参照、弱い値を持つマップなどを使用するであろうメモテーブル、キャッシュまたは他の何かには使用できない。

これは私には思えます:

weakmap.set(key, value);

...これはまさにこれを言うことの回り道の方法です:

key.value = value;

具体的なユースケースはありませんか。

342
valderman

基本的に

WeakMapsは、ガベージコレクションを妨げることなく、外部からオブジェクトを拡張する方法を提供します。オブジェクトを拡張したいが、それが封印されているためにできないときはいつでも - または外部ソースから - WeakMapを適用することができます。

WeakMapは、キーが弱いマップ(辞書)です。つまり、keyへのすべての参照が失われ、値への参照はこれ以上ありません - valueはガベージコレクションされる可能性があります。最初に例を通してこれを示して、それからそれを少し説明して、そして最後に実際の使用で終わりましょう。

特定のオブジェクトを提供するAPIを使用しているとしましょう。

var obj = getObjectFromLibrary();

今、私はオブジェクトを使用するメソッドがあります:

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

特定のオブジェクトに対してメソッドが何回呼び出されたかを追跡し、それがN回を超えて発生したかどうかを報告します。単純にMapを使うと思います:

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);
}

そして、メモリリークはなくなりました。

ユースケース

そうでなければメモリリークを引き起こし、WeakMapsによって可能になるいくつかのユースケースは次のとおりです。

  • 特定のオブジェクトに関する個人データを保持し、マップへの参照を持つ人々にのみそのオブジェクトへのアクセスを許可します。プライベートシンボルの提案にはもっと特別なアプローチがありますが、それは今から長い時間です。
  • ライブラリオブジェクトに関するデータを変更することなく、またはオーバーヘッドを招くことなく、ライブラリオブジェクトに関するデータを保持します。
  • JSエンジンが同じタイプのオブジェクトに使用する隠しクラスの問題を招かないように、このタイプのオブジェクトが多数存在する小さなオブジェクトのセットに関するデータを保持する。
  • DOMノードのようなHostオブジェクトに関するデータをブラウザに保存する。
  • 外部からオブジェクトに機能を追加する(他の答えのイベントエミッタの例のように)。

実際の使い方を見てみましょう

外側からオブジェクトを拡張するために使用できます。 Node.jsの実世界からの実用的な(適応された、本当のことを言う)例をあげましょう。

Node.jsで、Promiseオブジェクトがある - 今、あなたは現在拒否されているすべての約束を追跡したい - しかし、notとしたいそれらへの参照が存在しない場合のガベージコレクションから。

さて、あなたはdon'tを明白な理由でネイティブオブジェクトに追加したいのです - それであなたは立ち往生しています。約束を参照し続けると、ガベージコレクションが発生しないため、メモリリークが発生します。あなたが参照を守らないならば、あなたは個々の約束についての追加情報を保存することができません。約束のIDを本質的に保存することを含むどのような計画でも、あなたはそれへの参照が必要であることを意味します。

弱マップを入力

WeakMapsはキーが弱いことを意味します。弱いマップを列挙したり、そのすべての値を取得したりする方法はありません。弱いマップでは、キーに基づいてデータを格納し、キーがガベージコレクションされたときに値を格納することができます。

これは、約束があればそれについての状態を保存できるということです - そしてそのオブジェクトはまだガベージコレクション可能です。後で、オブジェクトへの参照を取得した場合は、それに関連する状態があるかどうかを確認して報告することができます。

これは Petka Antonovによって未処理の拒否フックthis として実装するために使用されました。

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
});

私たちは約束についての情報を地図に保存し、拒否された約束がいつ扱われたかを知ることができます。

443

この答えは偏っており、現実のシナリオでは使用できないようです。それをそのまま読んでください、そして実験以外の何のためにもそれを実際のオプションとして考えないでください

ユースケースはそれをリスナーのための辞書として使うことかもしれません、私はそれをした同僚を持っています。リスナーは、このようなやり方で直接ターゲットにされるので非常に役に立ちます。さようならlistener.on

しかし、より抽象的な観点から見れば、WeakMapは基本的なものへのアクセスを非マテリアライズするのに特に強力です。メンバーを分離するために名前空間は必要ありません。これはすでにこの構造の性質によるものです。厄介な冗長オブジェクトキーを置き換えることで、大幅なメモリの改善が可能になると確信しています(ただし、分解しても効果はありません)。


次は何を読む前に

Benjamin 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);
    }
}
46
axelduch

WeakMapはカプセル化と情報隠蔽に適しています

WeakMapはES6以降でのみ利用可能です。 WeakMapはキーがオブジェクトでなければならないキーと値のペアのコレクションです。次の例では、2つの項目を持つ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()メソッドを使用しました。 WeakMapsの興味深い側面は、マップ内のキーへの弱い参照を保持しているという事実です。弱い参照は、オブジェクトが破壊された場合、ガベージコレクタが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;
    }());
})()
15

私はWeakMapをパラメータとして不変オブジェクトを取り込む関数の心配のないメモのキャッシュとして使います。

メモ化は、「値を計算した後でキャッシュして、もう一度計算する必要がないようにする」というおしゃれな方法です。

これが例です:

// using immutable.js from here https://facebook.github.io/immutable-js/

const memo = new WeakMap();

let myObj = Immutable.Map({a: 5, b: 6});

function someLongComputeFunction (someImmutableObj) {
  // if we saved the value, then return it
  if (memo.has(someImmutableObj)) {
    console.log('used memo!');
    return memo.get(someImmutableObj);
  }
  
  // else compute, set, and return
  const computedValue = someImmutableObj.get('a') + someImmutableObj.get('b');
  memo.set(someImmutableObj, computedValue);
  console.log('computed value');
  return computedValue;
}


someLongComputeFunction(myObj);
someLongComputeFunction(myObj);
someLongComputeFunction(myObj);

// reassign
myObj = Immutable.Map({a: 7, b: 8});

someLongComputeFunction(myObj);
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js"></script>

注意すべき点がいくつかあります。

  • Immutable.jsオブジェクトは、変更すると(新しいポインタを使用して)新しいオブジェクトを返すので、WeakMapでキーとして使用しても同じ計算値が保証されます。
  • WeakMapはメモに最適です。なぜなら、オブジェクト(キーとして使用される)がガベージコレクションされると、計算された値も削除されるからです。
7
Rico Kahler

ウィークマップは、ガベージコレクションを妨げたり、同僚がコードを狂わせることなく、DOM要素に関するメタデータを格納するために使用できます。たとえば、Webページ内のすべての要素に数値インデックスを付けるためにそれらを使用できます。

WeakMapsまたはWeakSetがない場合

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[Math.pow(i, 2) % 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"
  )   // }
);

WeakMapsとWeakSetを使う:

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[Math.pow(i, 2) % 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"
  )   // }
);

違い

ウィークマップのバージョンが長いという事実を除けば、この違いはごくわずかに見えるかもしれませんが、上記の2つのコードには大きな違いがあります。最初のコードスニペットでは、弱いマップは含まれていませんが、コード要素はDOM要素間のあらゆる方法で参照を格納します。これにより、DOM要素がガベージコレクションされるのを防ぎます。 Math.pow(i, 2) % len]は誰も使用しないような奇妙なボールのように思えるかもしれませんが、もう一度考えてみてください。たくさんの本番コードには、ドキュメント全体にわたってバウンドするDOM参照があります。 2番目のコードでは、要素への参照がすべて弱いため、ノードを削除すると、ブラウザはそのノードが使用されていない(コードからアクセスできない)と判断できます。そのため、メモリから削除してください。メモリ使用量、およびメモリアンカー(未使用の要素がメモリに保持されるコードの最初のスニペットなど)を気にする必要がある理由は、メモリ使用量が増えるとブラウザのGC試行回数が増えることを意味するためです。ブラウザのクラッシュを回避する)とは、ブラウジングの経験が遅くなり、ブラウザがクラッシュすることを意味します。

これらのためのpolyfillに関しては、私は私自身のライブラリをお勧めします( ここ@ github にあります)。これは非常に軽量なライブラリで、他のpolyfillに見られるような複雑すぎるフレームワークなしでそれを単純にpolyfillします。

〜ハッピーコーディング!

6
Jack Giffin