471

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

弱いマップのキーは、対応する値への強い参照を作成するため、弱いマップに挿入された値は、そのキーがまだ生きている限り消えることはありません。メモ テーブルには使用できませんキャッシュや、通常弱い参照、弱い値を持つマップなどを使用するその他のもの。

これは私には思えます:

weakmap.set(key, value);

...これは単なる回りくどい言い方です:

key.value = value;

見逃している具体的なユースケースは何ですか?

4

8 に答える 8

601

基本的に

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

WeakMap は、キーが弱いマップ (ディクショナリ) です。つまり、キーへのすべての参照が失われ、値への参照がなくなった場合、はガベージ コレクションされる可能性があります。最初に例を通してこれを示し、次に少し説明し、最後に実際の使用で終わりましょう。

特定のオブジェクトを提供する 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 によって有効になるユース ケースには次のものがあります。

  • 特定のオブジェクトに関するプライベート データを保持し、マップへの参照を持つ人々にのみアクセスを許可します。private-symbols の提案では、よりアドホックなアプローチが予定されていますが、それにはまだ時間がかかります。
  • オブジェクトを変更したり、オーバーヘッドを発生させたりすることなく、ライブラリ オブジェクトに関するデータを保持します。
  • JS エンジンが同じタイプのオブジェクトに使用する隠しクラスで問題が発生しないように、同じタイプのオブジェクトが多数存在するオブジェクトの小さなセットに関するデータを保持します。
  • ブラウザー内の DOM ノードなどのホスト オブジェクトに関するデータを保持します。
  • 外部からオブジェクトに機能を追加します(他の回答のイベントエミッターの例のように)。

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

オブジェクトを外側から拡張するために使用できます。Node.js の現実の世界からの実用的な (適応された、一種の現実的な - 要点を説明するための) 例を挙げましょう。

Node.js を使用していて、オブジェクトを持っているとしましょう。Promise現在拒否されているすべての promise を追跡したいと考えています。ただし、オブジェクトへの参照が存在しない場合に、それらをガベージ コレクションから除外したくはありません。

さて、明らかな理由からネイティブ オブジェクトにプロパティを追加したくないので、行き詰まっています。プロミスへの参​​照を保持すると、ガベージ コレクションが発生しないため、メモリ リークが発生します。参照を保持しないと、個々の約束に関する追加情報を保存できません。promise の ID を保存することを含むスキームは、本質的にそれへの参照が必要であることを意味します。

WeakMaps に入る

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

つまり、promise を指定すると、それに関する状態を保存できます。また、そのオブジェクトは引き続きガベージ コレクションされる可能性があります。後で、オブジェクトへの参照を取得すると、それに関連する状態があるかどうかを確認して報告できます。

これは、次のように 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
});

プロミスに関する情報をマップに保持し、拒否されたプロミスがいつ処理されたかを知ることができます。

于 2015-04-02T15:09:00.997 に答える
14

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 で計算された値も収集されるため、 WeakMap はメモに最適です。
于 2017-09-17T11:25:08.440 に答える
10

この単純な機能ベースの使用例/WeakMaps の例があります。

ユーザーのコレクションを管理する

、、、および呼び出されたメソッドUserを含むオブジェクトのプロパティから始めました。このメソッドは、他のプロパティの人間が読める要約を出力します。fullnameusernameagegenderprint

/**
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次に、 をキーとする複数のユーザーのコレクションを保持するために呼び出される Map を追加しましたusername

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

コレクションの追加には、ユーザーを追加、取得、削除するためのヘルパー関数と、完全を期すためにすべてのユーザーを出力するための関数も必要でした。

/**
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();
    });
}

上記のすべてのコードをNodeJSusersで実行すると、プロセス全体で Mapのみがユーザー オブジェクトへの参照を持ちます。個々のユーザー オブジェクトへのその他の参照はありません。

例として、このコードをインタラクティブな NodeJS シェルで実行して、4 人のユーザーを追加して出力します。 ユーザーの追加と印刷

既存のコードを変更せずにユーザーに情報を追加

ここで、各ユーザーのソーシャル メディア プラットフォーム (SMP) リンクをユーザー オブジェクトと共に追跡する必要がある新しい機能が必要であるとします。

ここで重要なのは、既存のコードへの介入を最小限に抑えてこの機能を実装する必要があることです。

これは、次の方法で WeakMaps で可能です。

Twitter、Facebook、LinkedIn 用に 3 つの個別の WeakMap を追加します。

/*
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 WeakMap の一部またはすべてから SMP リンクが消去されます。これは、ユーザー オブジェクトがないと、その SMP リンクにアクセスする方法がないためです。

...例を続けると、ユーザーBillを削除し、Billが関連付けられていた SMP のリンクを出力します。

マップからユーザー Bill を削除すると、SMP リンクも削除されます

SMP リンクを個別に個別に削除するための追加コードは必要ありません。また、この機能が変更される前の既存のコードも変更されていません。

WeakMaps を使用する/使用しないでこの機能を追加する方法が他にある場合は、お気軽にコメントしてください。

于 2020-03-04T10:18:07.967 に答える