JavaScript | WeakMap / WeakSet の動作(GCで自動削除)を視覚化

javascrpit JavaScript
スポンサーリンク

WeakMap と Map を同じ操作で比較し、「WeakMap は自動的に解放されるが、Map は残り続ける」ことを擬似的に視覚化するデモを作りましょう。


WeakMap vs Map:画像キャッシュ比較デモ

以下のHTMLをコピーしてブラウザで開くと動きます。

See the Pen WeakMap vs Map Image Cache Comparison by MONO365 -Color your days- (@monoqlo365) on CodePen.

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>WeakMap vs Map 画像キャッシュ比較</title>
<style>
  body { font-family: sans-serif; padding: 1em; }
  h2 { margin-top: 1em; }
  button { margin: 5px; padding: 6px 12px; }
  .panel { display: flex; gap: 20px; }
  .box {
    flex: 1;
    background: #f7f7f7;
    border-radius: 8px;
    padding: 10px;
  }
  img { width: 100px; border-radius: 6px; margin: 4px; box-shadow: 0 0 4px #ccc; }
  .log { font-size: 0.9em; color: #333; background: #fff; padding: 8px; border-radius: 6px; margin-top: 10px; min-height: 50px; }
  .memory {
    margin-top: 10px;
    font-family: monospace;
    white-space: pre;
  }
</style>
</head>
<body>
<h1>WeakMap vs Map 画像キャッシュ比較</h1>

<div>
  <button id="load">画像を読み込み</button>
  <button id="release">参照を切る(WeakMap側だけ自動GC)</button>
</div>

<div class="panel">
  <div class="box">
    <h2>🧠 Map キャッシュ</h2>
    <div id="mapPreview"></div>
    <div class="log" id="mapLog"></div>
    <div class="memory" id="mapMemory"></div>
  </div>
  <div class="box">
    <h2>💨 WeakMap キャッシュ</h2>
    <div id="weakPreview"></div>
    <div class="log" id="weakLog"></div>
    <div class="memory" id="weakMemory"></div>
  </div>
</div>

<script>
const mapCache = new Map();
const weakCache = new WeakMap();
let mapImg = null;
let weakImg = null;

function updateMemoryView() {
  // 擬似的にMapのメモリ残留を表示
  const mapKeys = Array.from(mapCache.keys()).map((k, i) => "Image#" + (i+1));
  document.getElementById('mapMemory').textContent =
    "キャッシュ中: " + (mapKeys.length ? mapKeys.join(", ") : "なし");

  // WeakMapは実際のキー列挙ができないので、擬似表示
  document.getElementById('weakMemory').textContent =
    weakImg ? "キャッシュ中: Image#1(参照あり)" : "キャッシュ中: (なし・GC対象)";
}

document.getElementById('load').onclick = () => {
  // Map側
  mapImg = new Image();
  mapImg.src = "https://placekitten.com/120/120?" + Math.random();
  mapImg.onload = () => {
    document.getElementById('mapPreview').innerHTML = "";
    document.getElementById('mapPreview').appendChild(mapImg);
    mapCache.set(mapImg, "キャッシュ済み");
    document.getElementById('mapLog').textContent = "✅ 画像を読み込み → Map にキャッシュしました。";
    updateMemoryView();
  };

  // WeakMap側
  weakImg = new Image();
  weakImg.src = mapImg.src;
  weakImg.onload = () => {
    document.getElementById('weakPreview').innerHTML = "";
    document.getElementById('weakPreview').appendChild(weakImg);
    weakCache.set(weakImg, "キャッシュ済み");
    document.getElementById('weakLog').textContent = "✅ 画像を読み込み → WeakMap にキャッシュしました。";
    updateMemoryView();
  };
};

document.getElementById('release').onclick = () => {
  document.getElementById('weakPreview').innerHTML = "";
  document.getElementById('weakLog').textContent = "🧹 WeakMapの画像参照を切りました(GC対象)";
  weakImg = null;

  document.getElementById('mapLog').textContent = "⚠️ Map側は参照を切ってもデータ残留中(メモリリークの可能性)";
  updateMemoryView();
};
</script>
</body>
</html>
HTML

実行シナリオ

  1. 「画像を読み込み」を押す
     → 両方のキャッシュに画像を保存
  2. 「参照を切る」を押す
     → WeakMap側は自動的にGC対象(擬似的に消える)
     → Map側はキーを保持し続け、メモリに残留

学習ポイント

項目MapWeakMap
キーの型任意(プリミティブOK)オブジェクトのみ
キー参照強参照(残り続ける)弱参照(GCで自動削除)
イテレーション可能不可
主な用途永続データ保持一時キャッシュ・DOM関連付け
タイトルとURLをコピーしました