ECMAScript 6で導入されたWeakMap
データ構造の実際の用途は何ですか?
ウィークマップのキーは対応する値への強い参照を作成するので、ウィークマップに挿入された値がそのキーである限りneverが消えることを保証しますそれはまだ生きている、それはあなたが通常弱い参照、弱い値を持つマップなどを使用するであろうメモテーブル、キャッシュまたは他の何かには使用できない。
これは私には思えます:
weakmap.set(key, value);
...これはまさにこれを言うことの回り道の方法です:
key.value = value;
具体的なユースケースはありませんか。
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);
}
そして、メモリリークはなくなりました。
そうでなければメモリリークを引き起こし、WeakMap
sによって可能になるいくつかのユースケースは次のとおりです。
外側からオブジェクトを拡張するために使用できます。 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
});
私たちは約束についての情報を地図に保存し、拒否された約束がいつ扱われたかを知ることができます。
ユースケースはそれをリスナーのための辞書として使うことかもしれません、私はそれをした同僚を持っています。リスナーは、このようなやり方で直接ターゲットにされるので非常に役に立ちます。さようなら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);
}
}
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()
メソッドを使用しました。 WeakMap
sの興味深い側面は、マップ内のキーへの弱い参照を保持しているという事実です。弱い参照は、オブジェクトが破壊された場合、ガベージコレクタが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;
}());
})()
私は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>
注意すべき点がいくつかあります。
ウィークマップは、ガベージコレクションを妨げたり、同僚がコードを狂わせることなく、DOM要素に関するメタデータを格納するために使用できます。たとえば、Webページ内のすべての要素に数値インデックスを付けるためにそれらを使用できます。
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"
) // }
);
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します。
〜ハッピーコーディング!