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実行シナリオ
- 「画像を読み込み」を押す
→ 両方のキャッシュに画像を保存 - 「参照を切る」を押す
→ WeakMap側は自動的にGC対象(擬似的に消える)
→ Map側はキーを保持し続け、メモリに残留
学習ポイント
| 項目 | Map | WeakMap |
|---|---|---|
| キーの型 | 任意(プリミティブOK) | オブジェクトのみ |
| キー参照 | 強参照(残り続ける) | 弱参照(GCで自動削除) |
| イテレーション | 可能 | 不可 |
| 主な用途 | 永続データ保持 | 一時キャッシュ・DOM関連付け |


