【JavaScript】WeakMap / WeakSet って何？

先日、`map`と`set`について調べていると、`WeakMap` `WeakSet`というワードが出てきました。

今回はこの耳なじみがない`WeakMap` `WeakSet`について調べてみました。

# WeakMapについて
## WeakMapとは
`WeakMap`とは何でしょうか。
お馴染みのMDNには下記のように記載されています。

> WeakMap オブジェクトは、キーが弱く参照されるキーと値の組のコレクションです。キーはオブジェクトでなければならず、値は任意の値を取ることができます。

「**キーが弱く参照される**」というのがキーワードですね。
では特徴を見ていきましょう。

## WeakMapの特徴
1. **キーはオブジェクトのみ**
    プリミティブ型は使用できません
2. **キーの弱い参照**
    `WeakMap` 内のキーはほかに参照されていない場合、自動的にガーベジコレクションされ、メモリリークを防ぐことができる
3. **サイズやイテレーション不可**
    `WeakMap` は内容を列挙することができない（`for...of` や `size` プロパティを使用できない）

## WeakMapの使用例

実際の使用例を見てみましょう

**①プライベートデータの隠蔽**
オブジェクトキーを持っていないとデータにアクセスすることができません。
これにより、プライベートデータを保存するのに役立ちます。

```js
// WeakMapを使用してオブジェクトのプライベートデータを管理
const privateData = new WeakMap();

class User {
  constructor(name) {
    // ユーザー情報をプライベートデータとして保存
    privateData.set(this, { name });
  }

  getName() {
    // プライベートデータにアクセス
    return privateData.get(this).name;
  }
}

const user = new User('Alice');
console.log(user.getName()); // "Alice"

// オブジェクトを知っている場合のみアクセス可能
console.log(privateData.get(user)); // { name: "Alice" }

// 直接全体を見る手段がない（列挙不可）
console.log([...privateData]); // TypeError: privateData is not iterable

```

<br />

**②DOM要素に関連データを紐づける**
DOM要素が削除されると、その要素に紐づけられていたデータもメモリから解放されます。
これによりメモリリークを防止できます。

```js
// DOM要素に関連するデータを管理
const elementData = new WeakMap();

const button = document.createElement('button');
elementData.set(button, { clicked: 0 });

button.addEventListener('click', () => {
  // ボタンがクリックされた回数を記録
  const data = elementData.get(button);
  data.clicked++;
  console.log(`Button clicked ${data.clicked} times`);
});

document.body.appendChild(button);

// 5秒後にボタンを削除
// ボタンが削除されると関連データも自動的にガベージコレクションされる
setTimeout(() => {
  document.body.removeChild(button);
  console.log('Button removed from DOM');
}, 5000);

```

## WeakMapのメリット

`WeakMap` には以下のメリットがあるようです。
- 自動的なメモリ解放でメモリリークを防止
- プライベートデータの安全な管理

# WeakSetについて

## WeakSetとは

MDNには下記のように記載されています。

> WeakSet オブジェクトは、コレクションに弱く参照されたオブジェクトを格納することができます。

「**弱く参照されたオブジェクト**」というのがキーワードですね。
では特徴を見ていきましょう。

## WeakSetの特徴
1. **オブジェクトのみを格納可能**
  プリミティブ型は格納できません
2. **弱い参照を持つ**
  `WeakSet` に追加されたオブジェクトがほかの場所で参照されなくなると、自動的にガーベッジコレクションの対象になります
3. **イテレーション不可**
  通常の `Set` とは異なり、`WeakSet` の内容を列挙したり取得することはできません

<br />

基本的に `WeakMap` の特徴と同じですね。

## WeakSetの使用例

こちらも実際の使用例を見てみましょう

**①DOM要素の追跡**
`WeakSet` を使ってすでに処理済みのDOM要素を追跡します。
ボタンが削除されると、自動的に `WeakSet` からも削除されます。

```js
// WeakSetを作成
const processedElements = new WeakSet();

const button1 = document.createElement('button');
const button2 = document.createElement('button');

button1.textContent = 'Button 1';
button2.textContent = 'Button 2';

document.body.appendChild(button1);
document.body.appendChild(button2);

// イベントリスナーで処理済み要素を追跡
button1.addEventListener('click', () => {
  if (!processedElements.has(button1)) {
    console.log('Button 1 clicked for the first time!');
    processedElements.add(button1); // 処理済みに追加
  } else {
    console.log('Button 1 was already processed.');
  }
});

button2.addEventListener('click', () => {
  if (!processedElements.has(button2)) {
    console.log('Button 2 clicked for the first time!');
    processedElements.add(button2);
  } else {
    console.log('Button 2 was already processed.');
  }
});

```

<br />

**②オブジェクトの一時的な記録**
特定の状態のオブジェクトを一時的に追跡する場合に使用できます。

```js
// WeakSetを作成
const visitedPlayers = new WeakSet();

// プレイヤーオブジェクト
const player1 = { name: 'Kenny' };
const player2 = { name: 'Bob' };

// プレイヤーがエリアを訪れた場合に記録
function visitArea(player) {
  if (visitedPlayers.has(player)) {
    console.log(`${player.name} has already visited this area.`);
  } else {
    console.log(`${player.name} visits the area for the first time.`);
    visitedPlayers.add(player);
  }
}

visitArea(player1); // Kenny visits the area for the first time.
visitArea(player2); // Bob visits the area for the first time.
visitArea(player1); // Kenny has already visited this area.

```

## WeakSetのメリット
`WeakSet` には以下のようなメリットがあるようです。
- 自動的なメモリ解放でメモリリークを防止
- データの隠蔽やセキュリティの向上

# まとめ
`WeakMap` / `WeakSet` どちらも大きなメリットは、
自動的にメモリが解放される点です。 

そのため、一時的に使用する情報を保持する際に最も役立つのかなと思いました。

それでは。

# 参考文献
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/WeakMap

https://zenn.dev/oreo2990/articles/d290ea0f08a3e0

https://rinblog.org/javascript-new-weakmap-methods/

https://www.php.cn/ja/faq/1796539629.html

https://ja.javascript.info/weakmap-weakset