JavaScript | ES6+ 文法:新データ構造 – WeakMap

JavaScript JavaScript
スポンサーリンク

WeakMap とは何か(まずイメージから)

WeakMap は、
「キーをオブジェクトに限定した、”弱い紐づけ” をするための特別な Map」 です。

普通の Map と似ていますが、決定的に違うのはここです。

  • Map:キーに何でも使えるし、キーが残っている限り値も残る
  • WeakMapキーは「オブジェクトだけ」OK。そのオブジェクトが他で使われなくなったら、自動的にこのエントリも消える(ガベージコレクション)

つまり WeakMap は、

「あるオブジェクトに、後から “こっそり追加情報” をくっつけたいけど、
そのオブジェクトが不要になったら、追加情報も一緒に自動で消えてほしい」

というときに使う仕組みです。

ここが重要です。
WeakMap は「メモリリークを防ぎながら、オブジェクトに付随情報を持たせる」のが本来の目的 で、
ふつうの「辞書」として使うには向いていません。

普通の Map と WeakMap の違い(ここをしっかり)

違い1:キーにできるのは「オブジェクトだけ」

const wm = new WeakMap();

const obj = { id: 1 };

wm.set(obj, "何かのデータ"); // OK

wm.set(123, "数値キー");      // エラー:WeakMap のキーはオブジェクトだけ
wm.set("name", "Alice");     // エラー:同上
JavaScript

WeakMap のキーは、必ず「オブジェクト(配列や関数も含む)」でなければなりません。
プリミティブ値(数値・文字列・boolean など)はキーにできません。

Map はどんな値もキーにできるのに対して、WeakMap はかなり制限されています。

違い2:「キーのオブジェクト」が他で使われなくなったら自動的に消える

これが一番重要なポイントです。

let obj = { id: 1 };
const wm = new WeakMap();

wm.set(obj, "追加情報");

// どこかのタイミングで
obj = null; // obj への参照を切る(他に参照がなければ GC 対象)

// しばらくすると、Garbage Collector が働いて
// 元の { id: 1 } オブジェクトがメモリから回収される
// それに紐づいて、WeakMap の中のそのエントリも自動的に消える
JavaScript

ここで重要なのは:

  • Map の場合:キーとして使っている限り、そのオブジェクトは残り続ける(メモリリークの原因になりうる)
  • WeakMap の場合:他のどこからも参照されなくなったオブジェクトは、WeakMap に入っていても回収対象になる

WeakMap は、「オブジェクトの寿命に追随する付属情報」を持たせるための仕組みと考えると理解しやすいです。

違い3:イテレーションできない(for…of できない)

Map と違って、WeakMap は「中身を全部なめる」ことができません。

const wm = new WeakMap();
const obj1 = {};
const obj2 = {};

wm.set(obj1, 1);
wm.set(obj2, 2);

// for (const [k, v] of wm) {}   // これはエラー
// wm.keys() や wm.values() もない
JavaScript

for...offorEachkeys などがありません。

なぜかというと、

  • GC(ガベージコレクション)のタイミングは自由
  • いつの間にかキーが消えているかもしれない
  • そういう「消えるかもしれないもの」の一覧を常に提供するのは矛盾する

という設計上の理由があります。

ここが重要です。
WeakMap は「こっそり付随情報を持たせる」ためのものであって、「全体を一覧したりサイズを数えたりする用途には向いていない」 のです。

違い4:size プロパティがない

Map には size がありましたが、WeakMap にはありません。

const wm = new WeakMap();
// wm.size; // そんなものはない(エラー)
JavaScript

これも「いつ GC によって減るか分からない」性質から来ています。
WeakMap の中身は「数えない・見ない」「必要なキーを知っている前提で、ピンポイントでアクセスする」使い方になります。

WeakMap の基本操作(set / get / has / delete)

new WeakMap() で作る

const wm = new WeakMap();
JavaScript

set で追加(キーはオブジェクトのみ)

const wm = new WeakMap();
const user = { id: 1, name: "Alice" };

wm.set(user, { lastLogin: "2025-01-01" }); // OK
JavaScript

get / has / delete も Map と同じ形

console.log(wm.get(user)); // { lastLogin: "2025-01-01" }

console.log(wm.has(user)); // true

wm.delete(user);           // 削除
console.log(wm.has(user)); // false
JavaScript

API の形は Map とよく似ていますが、「キーがオブジェクト限定」「サイズが取れない」「ループできない」という大きな制約があります。

WeakMap が活きる場面(ここが本質)

パターン1:オブジェクトに「外から」付随データをつけたい

例えば、「DOM 要素ごとに追加情報を持たせたい」が、
DOM 自体にはプロパティを増やしたくないし、
DOM が削除されたら追加情報も自動で消えてほしい、という場合。

// DOM 要素ごとに追加情報をメモしておく WeakMap
const elementData = new WeakMap();

function setElementData(el, data) {
  elementData.set(el, data);
}

function getElementData(el) {
  return elementData.get(el);
}
JavaScript

使い方イメージ:

const div = document.createElement("div");
setElementData(div, { clicked: 0 });

div.addEventListener("click", () => {
  const info = getElementData(div);
  info.clicked++;
  console.log("クリック回数:", info.clicked);
});

// 後で DOM を破棄する
document.body.removeChild(div);
// 他に div を参照していなければ、
// div とそれに紐づく WeakMap のデータは GC で回収される
JavaScript

ここが重要です。
WeakMap は「元のオブジェクトに直接プロパティを足したくないが、関連情報を持ちたい」場面で使える
しかもメモリリークを起こしにくい形で。

パターン2:クラスインスタンスの「内部状態」を隠す

プライベートフィールド # が登場する前は、
WeakMap を使って「本物のプライベート状態」を実現するパターンがよくありました。

const _private = new WeakMap();

class User {
  constructor(name) {
    const privateData = { nameUpper: name.toUpperCase() };
    _private.set(this, privateData);

    this.name = name; // 公開プロパティ
  }

  get upperName() {
    return _private.get(this).nameUpper;
  }
}

const u = new User("Alice");
console.log(u.upperName); // ALICE

// _private の中身には外からアクセスできない
// u._private などもない
JavaScript

今はクラスの #フィールド を使うほうが素直ですが、
クラス外で「特定のインスタンスにだけ紐づく隠し情報」を持ちたいときには、
WeakMap が今も有効です。

パターン3:キャッシュ(メモ化)でメモリリークを防ぎたい

関数の結果をキャッシュして再利用する「メモ化」というテクニックがあります。

例えば、オブジェクトを元に重い計算をするとき:

const cache = new WeakMap();

function heavyCalc(obj) {
  if (cache.has(obj)) {
    return cache.get(obj);
  }

  // 実際はもっと重い処理…
  const result = JSON.stringify(obj);

  cache.set(obj, result);
  return result;
}
JavaScript

この場合、obj がどこからも参照されなくなれば、
cache 内のエントリも GC により消えます。

普通の Map でキャッシュすると、
「Map がずっと obj を参照し続ける → obj が永遠に生き続ける → メモリリーク」
となりかねませんが、WeakMap ならそれを避けられます。

ここが重要です。
「オブジェクト → 計算結果」のキャッシュを長期的に保持したいけれど、
使われなくなったオブジェクトに紐づくキャッシュは捨ててよい」
という状況で WeakMap はとても役立つ
です。

初心者が意識しておきたい「注意点」

単なる「キーがオブジェクトの Map」としては使わないほうがよい

「キーがオブジェクトの Map が欲しいんだよね」と思って WeakMap を選ぶと、
だいたい痛い目を見ます。

理由は:

  • ループできない(中身を一覧できない)
  • サイズが分からない
  • いつエントリが消えるか分からない

という制限のせいで、「ふつうの辞書」としてはとても使いにくいからです。

「キーがオブジェクトの辞書」が欲しいなら、
まずは 普通の Map を選ぶのが正解です。

WeakMap は、「ガベージコレクションとメモリリーク」に意識が向いてきた頃に、
「オブジェクトにぶらさげる“弱い紐づけ”」として使うものだと思ってください。

「いつ消えるか」はプログラマーからは見えない

WeakMap のエントリが消えるタイミングは、
JavaScript エンジンのガベージコレクタに完全に委ねられています。

  • すぐに消えるかもしれない
  • しばらく残っているかもしれない

これは仕様として保証されていませんし、されてはいけません。

そのため、

  • 「何個入っているか」でロジックを組まない
  • 「全部を列挙して保存し直す」といったことを考えない

ことが大事です。

ここが重要です。
WeakMap を使うときは、「中身を数えない・覗かない・列挙しない」という前提を受け入れる必要がある
あくまで「既知のオブジェクトをキーとして、追加情報を紐づける」用途に限定するのが安全です。

まとめ

WeakMap の核心は、
「キーにオブジェクトだけを使い、そのオブジェクトが不要になったら、自動的に WeakMap 側のデータも捨ててもらえる、メモリに優しい Map」
という点です。

押さえておきたいポイントは:

  • キーはオブジェクトだけ(数値や文字列はキーにできない)
  • そのキーオブジェクトが他で参照されなくなれば、WeakMap 内のエントリも GC により自動で消える
  • ループ(for…of)、keys/values/entries、size などは一切使えない
  • API は set / get / has / delete が中心で、既知のオブジェクトに対してピンポイントに使う
  • DOM 要素やクラスインスタンスなどに「外からこっそり付随情報を持たせたいとき」、キャッシュのメモリリークを避けたいときに特に役立つ
  • 「キーがオブジェクトの辞書が欲しい」だけなら WeakMap ではなく普通の Map を使う

初心者のうちは、まず MapSet をしっかり使いこなせるようになってから、
「オブジェクトに追加情報を付けたいけど、メモリリークさせたくない」というシチュエーションが見えてきたタイミングで、
WeakMap を「そういえばあったな」と思い出してもらえれば十分です。

そのときは、「Strong(普通の)Map」と「WeakMap」がメモリと寿命に対してどう違うかを意識しながら、
実際に小さなサンプルを書いて挙動を確かめてみてください。

タイトルとURLをコピーしました