以下は WeakMap / WeakSet の「自動ガベージコレクション(GC)によりエントリが消える様子を視覚化するデモページ」 です。
実際のブラウザでは GC の発生タイミングは制御できないため、FinalizationRegistry が使える場合は可能な限り実際の GC を検出して反映し、使えない場合は「シミュレーション(手動で参照を切ってから擬似的に回収する)」モードで振る舞います。
(注意:実際の GC はブラウザ任せで非決定的です — ページ内にその旨の説明と代替の手動シミュレーションを入れています。)
以下をファイル(例: weak-gc-sim.html)として保存してブラウザで開いてください。
See the Pen WeakMap / WeakSet Automatic GC Simulation by MONO365 -Color your days- (@monoqlo365) on CodePen.
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>WeakMap / WeakSet 自動GCシミュレーション</title>
<style>
body { font-family: "Segoe UI", sans-serif; background: #fafcff; color: #222; padding: 20px; }
h1 { color: #0b5fff; }
.grid { display:flex; gap:20px; flex-wrap:wrap; }
.panel { background:#fff; border:1px solid #e3e8ff; padding:16px; border-radius:10px; width: 480px; box-shadow: 0 4px 12px rgba(6,24,78,0.05); }
.controls { display:flex; gap:8px; margin-bottom:12px; flex-wrap:wrap; }
input[type="text"] { padding:8px; border:1px solid #cfd8ff; border-radius:8px; width:160px; }
button { padding:8px 12px; border-radius:8px; border:none; background:#0b5fff; color:white; cursor:pointer; }
button.ghost { background:#6b7280; }
.list { margin-top:10px; padding:8px; background:#f6f9ff; border-radius:8px; min-height:40px; }
.item { padding:6px 8px; margin:6px 0; background:#eef4ff; border-radius:6px; display:flex; justify-content:space-between; align-items:center; }
.small { font-size:0.9em; color:#555; }
.log { margin-top:12px; background:#f3f7ff; padding:8px; border-radius:8px; height:110px; overflow:auto; font-size:0.9em; }
.note { margin-top:12px; background:#fff7d6; padding:10px; border-radius:8px; border-left:4px solid #ffd24d; }
.status { font-weight:700; color:#0b5fff; }
</style>
</head>
<body>
<h1>🔁 WeakMap / WeakSet — 自動GC シミュレーション</h1>
<p class="small">注: 実際のガベージコレクション(GC)のタイミングはブラウザが管理します。<br>
もし <code>FinalizationRegistry</code> が利用可能なら、GC 後に通知を受け取って視覚的に反映します。利用不可なら手動(シミュレーション)で「参照を切って回収」を行えます。</p>
<div class="grid">
<!-- Objects 管理 -->
<div class="panel">
<h3>1) オブジェクト生成(強参照)</h3>
<div class="controls">
<input id="objName" placeholder="ラベル (例: objA)" />
<button id="createObj">オブジェクトを作る</button>
<button class="ghost" id="clearAll">全ての強参照をクリア</button>
</div>
<div class="small">現在の<strong>強参照リスト</strong>(ここにオブジェクトを保持している=GC の対象にならない):</div>
<div id="strongList" class="list"></div>
<div class="small">操作例: オブジェクトを作り、WeakMap/WeakSet に追加 → 強参照を切る →(GC が来れば)自動で Weak 側から消える</div>
</div>
<!-- Collections -->
<div class="panel">
<h3>2) Collections に追加(Map / Set / WeakMap / WeakSet)</h3>
<div class="controls">
<select id="selectObj"></select>
<input id="valueInput" placeholder="任意の値 (Map の値)" />
<button id="addToMap">Map.set()</button>
<button id="addToWeakMap">WeakMap.set()</button>
<button id="addToSet">Set.add()</button>
<button id="addToWeakSet">WeakSet.add()</button>
</div>
<div class="small">現在の内容(Map / Set は列挙可能、WeakMap / WeakSet は列挙不可だがここでは「状態表示」をシミュレート):</div>
<div style="display:flex; gap:8px; margin-top:10px;">
<div style="flex:1">
<div class="small">Map(列挙可)</div>
<div id="mapList" class="list"></div>
</div>
<div style="flex:1">
<div class="small">Set(列挙可)</div>
<div id="setList" class="list"></div>
</div>
</div>
<div style="display:flex; gap:8px; margin-top:10px;">
<div style="flex:1">
<div class="small">WeakMap(実際は列挙不可)</div>
<div id="weakMapList" class="list"></div>
</div>
<div style="flex:1">
<div class="small">WeakSet(実際は列挙不可)</div>
<div id="weakSetList" class="list"></div>
</div>
</div>
<div class="log" id="opLog"></div>
</div>
<!-- GC / シミュレーション -->
<div class="panel">
<h3>3) 参照を切る・GC を試す</h3>
<div class="controls">
<button id="dropRef">選択オブジェクトの強参照を切る(weak のみ残す)</button>
<button id="simulateGC">(手動)擬似GC を実行</button>
<button id="showStatus">状態チェック</button>
</div>
<div class="small">FinalizationRegistry の利用可否: <span id="frStatus" class="status"></span></div>
<div class="small">説明:</div>
<div class="note">
<strong>重要:</strong> ブラウザは GC をいつ実行するか決めます。<br>
- <code>FinalizationRegistry</code> があれば、GC 後にコールバックが呼ばれ、実際に WeakMap/WeakSet のエントリが消えたことを検出できます。<br>
- それがなくても、ここでは「擬似GC」ボタンで UI 上から Weak 側の未参照エントリを掃除する操作を実行できます(教育目的のシミュレーション)。
</div>
<div style="margin-top:12px;">
<div class="small">ログ(GC 通知 / 操作):</div>
<div class="log" id="gcLog"></div>
</div>
</div>
</div>
<script>
(function(){
// データ構造
const strongRefs = new Map(); // ラベル -> object (強参照)
const myMap = new Map();
const mySet = new Set();
const myWeakMap = new WeakMap();
const myWeakSet = new WeakSet();
// UI 要素
const strongList = document.getElementById("strongList");
const selectObj = document.getElementById("selectObj");
const mapList = document.getElementById("mapList");
const setList = document.getElementById("setList");
const weakMapList = document.getElementById("weakMapList");
const weakSetList = document.getElementById("weakSetList");
const opLog = document.getElementById("opLog");
const gcLog = document.getElementById("gcLog");
const frStatus = document.getElementById("frStatus");
const canFinalization = typeof FinalizationRegistry === "function";
frStatus.textContent = canFinalization ? "利用可能" : "利用不可(擬似GCモード)";
// FinalizationRegistry を使う場合は登録して、GC 検出時に UI を更新する
let registry = null;
if (canFinalization) {
registry = new FinalizationRegistry((heldValue) => {
// heldValue は登録時に渡した識別子など
logGC(`🗑️ FinalizationRegistry: 回収されたオブジェクト "${heldValue}" を検知`);
// 実際の WeakMap/WeakSet では、キーが回収されると自動でエントリが無くなるため
// ここでは UI 上の表示(weakMapList/weakSetList)を掃除して反映する
cleanupWeakUI();
});
}
// --- ヘルパ関数 ---
function logOp(msg) {
const time = new Date().toLocaleTimeString();
opLog.innerHTML = `<div class="small">[${time}] ${msg}</div>` + opLog.innerHTML;
}
function logGC(msg) {
const time = new Date().toLocaleTimeString();
gcLog.innerHTML = `<div class="small">[${time}] ${msg}</div>` + gcLog.innerHTML;
}
// UI 更新
function updateStrongList() {
strongList.innerHTML = "";
selectObj.innerHTML = "";
for (const [label,obj] of strongRefs.entries()) {
const d = document.createElement("div");
d.className = "item";
d.innerHTML = `<span>${label}</span><button data-label="${label}" class="ghostBtn">参照を切る</button>`;
strongList.appendChild(d);
const opt = document.createElement("option");
opt.value = label;
opt.textContent = label;
selectObj.appendChild(opt);
}
// 参照切りボタンにイベント
strongList.querySelectorAll(".ghostBtn").forEach(b => {
b.addEventListener("click", () => {
const label = b.dataset.label;
dropReferenceByLabel(label);
});
});
}
// Map / Set 列挙表示
function renderMapSet() {
// Map
mapList.innerHTML = "";
myMap.forEach((v,k) => {
const div = document.createElement("div");
div.className = "item";
div.textContent = `${k} → ${v}`;
mapList.appendChild(div);
});
// Set
setList.innerHTML = "";
mySet.forEach(v => {
const div = document.createElement("div");
div.className = "item";
div.textContent = `${v}`;
setList.appendChild(div);
});
}
// Weak 側は列挙不可だが教育用に状態表示を作る(内部の参照リストを持つ)
const weakMapDebug = []; // [{label, keyRefDescription, value}]
const weakSetDebug = []; // [{label, keyRefDescription}]
function renderWeakDebug() {
weakMapList.innerHTML = "";
weakSetList.innerHTML = "";
weakMapDebug.forEach(e => {
const div = document.createElement("div");
div.className = "item";
div.textContent = `${e.label} : key="${e.keyDesc}" -> ${e.value}`;
weakMapList.appendChild(div);
});
weakSetDebug.forEach(e => {
const div = document.createElement("div");
div.className = "item";
div.textContent = `${e.label} : key="${e.keyDesc}"`;
weakSetList.appendChild(div);
});
}
// cleanup: 強参照が無い(= GC の候補)エントリを weakDebug から取り除く(シミュレーション)
function cleanupWeakUI() {
// strongRefs に存在しないラベルを weakDebug から削除
const aliveLabels = new Set(strongRefs.keys());
let removed = false;
for (let i = weakMapDebug.length - 1; i >= 0; i--) {
if (!aliveLabels.has(weakMapDebug[i].label)) {
logGC(`🔎 weakMapDebug: "${weakMapDebug[i].label}" を回収候補として UI から削除`);
weakMapDebug.splice(i,1);
removed = true;
}
}
for (let i = weakSetDebug.length - 1; i >= 0; i--) {
if (!aliveLabels.has(weakSetDebug[i].label)) {
logGC(`🔎 weakSetDebug: "${weakSetDebug[i].label}" を回収候補として UI から削除`);
weakSetDebug.splice(i,1);
removed = true;
}
}
if (removed) renderWeakDebug();
}
// --- 操作ハンドラ ---
// オブジェクト作成
document.getElementById("createObj").addEventListener("click", () => {
const label = document.getElementById("objName").value.trim() || `obj${Date.now()%10000}`;
// 新しいオブジェクト(ダミー)
const obj = { __label: label, created: Date.now() };
strongRefs.set(label, obj);
updateStrongList();
logOp(`オブジェクト "${label}" を作成(強参照を保持)`);
document.getElementById("objName").value = "";
});
// 全強参照クリア
document.getElementById("clearAll").addEventListener("click", () => {
strongRefs.clear();
updateStrongList();
logOp("全ての強参照をクリア(全オブジェクトが GC 対象になり得る)");
});
// 追加ボタン: Map / WeakMap / Set / WeakSet
document.getElementById("addToMap").addEventListener("click", () => {
const sel = selectObj.value;
const val = document.getElementById("valueInput").value || "(no value)";
if (!sel) { alert("先にオブジェクトを作って選択してください"); return; }
const keyObj = strongRefs.get(sel);
myMap.set(sel, val); // Map: we use label as key for readability (could use obj)
logOp(`Map.set("${sel}", "${val}")`);
renderMapSet();
});
document.getElementById("addToWeakMap").addEventListener("click", () => {
const sel = selectObj.value;
const val = document.getElementById("valueInput").value || "(no value)";
if (!sel) { alert("先にオブジェクトを作って選択してください"); return; }
const keyObj = strongRefs.get(sel);
myWeakMap.set(keyObj, val); // WeakMap のキーは実際のオブジェクト
weakMapDebug.push({ label: sel, keyDesc: `Object(${sel})`, value: val });
// FinalizationRegistry に登録(heldValue にラベルを渡す)
if (registry) registry.register(keyObj, sel);
logOp(`WeakMap.set(<object ${sel}>, "${val}")(WeakMap に追加)`);
renderWeakDebug();
});
// Set
document.getElementById("addToSet").addEventListener("click", () => {
const val = (document.getElementById("valueInput").value || `val${Date.now()%1000}`);
mySet.add(val);
logOp(`Set.add("${val}")`);
renderMapSet();
});
// WeakSet
document.getElementById("addToWeakSet").addEventListener("click", () => {
const sel = selectObj.value;
if (!sel) { alert("先にオブジェクトを作って選択してください"); return; }
const keyObj = strongRefs.get(sel);
myWeakSet.add(keyObj);
weakSetDebug.push({ label: sel, keyDesc: `Object(${sel})` });
if (registry) registry.register(keyObj, sel + "_ws");
logOp(`WeakSet.add(<object ${sel}>)`);
renderWeakDebug();
});
// 参照を切る(指定된 オブジェクトの強参照を削除)
function dropReferenceByLabel(label) {
if (!strongRefs.has(label)) { alert("そのラベルは存在しません"); return; }
strongRefs.delete(label);
updateStrongList();
logOp(`"${label}" の強参照を削除(これで Weak 側が回収対象に)`);
}
// ドロップボタン
document.getElementById("dropRef").addEventListener("click", () => {
const sel = selectObj.value;
if (!sel) { alert("先にオブジェクトを作って選択してください"); return; }
dropReferenceByLabel(sel);
});
// 擬似GC(手動)
document.getElementById("simulateGC").addEventListener("click", () => {
// 実際は GC を強制できないため、ここでは strongRefs に存在しないラベルを weakDebug から削除して UI を更新
logGC("🔁 擬似GC を実行(強参照が無いオブジェクトを weak デバッグ表示から削除)");
cleanupWeakUI();
// さらに FinalizationRegistry が使える環境では、GC が実際に走れば registry が呼ばれる。
if (!registry) {
logGC("(注意)FinalizationRegistry が利用できないため、これは教育目的の擬似削除です。");
}
});
// 状態チェック(現在の Map/Set と debug の状態を表示)
document.getElementById("showStatus").addEventListener("click", () => {
renderMapSet();
renderWeakDebug();
logOp("現在の状態を表示(Map/Set/weak デバッグ表示)");
});
// ---- 初期レンダー ----
updateStrongList();
renderMapSet();
renderWeakDebug();
})();
</script>
</body>
</html>
HTML使い方メモ(短く)
- 「ラベル」を入力してオブジェクトを作る(強参照が
strongListに入る)。 - そのオブジェクトを選び、
WeakMap.set()/WeakSet.add()(またはMap.set()/Set.add())に追加。 - 「参照を切る」(drop) して強参照を無くす。
- ブラウザが GC を実行すれば、
FinalizationRegistryがある環境では回収を検出して自動で UI が更新される。無ければ「擬似GC」ボタンで教育的に回収処理を行えます。
注意点(重要)
- 実際の GC の発生タイミングはブラウザ環境に依存し、強制はできません。FinalizationRegistry は GC 後に通知を受け取るための仕組みですが、通知が必ずすぐ来るわけではありません。
- WeakMap / WeakSet は 列挙できない(
for..ofなどで中身を取り出せない)のが仕様です。本デモでは教育用に内部の状態をweakMapDebug/weakSetDebugとして可視化しています(これは実際の API の振る舞いを模擬するためのものです)。


