実際の ガベージコレクション(GC)をブラウザで強制実行することはできないため、このデモは 「WeakMap / WeakSet の挙動を分かりやすく視覚化して理解するためのシミュレーション」 として作っています。
重要:実際の WeakMap / WeakSet は列挙できず、GC の発生時期も不定です。本デモは「外部参照を切ると WeakMap/WeakSet のエントリは最終的に消える」という概念を 安全に・分かりやすく示すためのものです(ブラウザで確実に消えたことを検証するものではありません)。
以下をブラウザで保存して開いてください — UI でオブジェクトを作り、外部参照を「切る」と(シミュレーションで)自動的にエントリが消えます。右側の「実際の WeakMap/WeakSet」には実際の WeakMap/WeakSet への格納操作も行いますが、可視化は並行して保持する visualMap / visualSet で行っています。
See the Pen WeakMap / WeakSet Behavior (Conceptual 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 動作(概念シミュレーション)</title>
<style>
body { font-family: "Segoe UI",sans-serif; background:#f7fbff; color:#222; padding:20px; }
h1 { color:#0078d7; }
.panels { display:flex; gap:20px; flex-wrap:wrap; }
.panel { flex:1 1 320px; background:#fff; border:2px solid #dceffd; border-radius:10px; padding:12px; box-shadow:0 2px 6px rgba(0,0,0,0.06); }
.controls { display:flex; gap:8px; margin-bottom:10px; }
button { background:#0078d7; color:#fff; border:none; padding:8px 10px; border-radius:8px; cursor:pointer; }
input { padding:8px; border:1px solid #cfe8ff; border-radius:8px; }
.list { margin-top:8px; min-height:60px; }
.entry { background:#eef8ff; margin:6px 0; padding:8px; border-radius:8px; display:flex; justify-content:space-between; align-items:center; }
.muted { color:#666; font-size:0.9em; }
.badge { padding:4px 8px; border-radius:12px; background:#0078d7; color:#fff; font-size:0.85em; }
.log { background:#f0fbff; padding:8px; border-left:4px solid #0078d7; max-height:140px; overflow:auto; margin-top:8px; border-radius:6px; }
.note { margin-top:10px; font-size:0.95em; background:#fffceb; padding:8px; border-left:4px solid #ffcc00; border-radius:6px; }
</style>
</head>
<body>
<h1>🔎 WeakMap / WeakSet 挙動(概念シミュレーション)</h1>
<p class="muted">注:実際のガベージコレクションは非決定的で強制できません。このデモは概念を視覚化するためのシミュレーションです。</p>
<div class="panels">
<!-- コントロール -->
<div class="panel">
<h2>操作パネル</h2>
<div style="margin-bottom:8px;">
<input id="objName" placeholder="オブジェクト名(例: domNode1)" />
<button id="create">オブジェクト作成</button>
</div>
<div class="muted">作成したオブジェクト(外部参照):</div>
<div id="handles" class="list"></div>
<div style="margin-top:10px;">
<button id="dropAll">すべての外部参照を切る(simulate)</button>
<button id="runSimGC">シミュレートされたGCを実行</button>
</div>
<div class="note">
<strong>説明:</strong>
<ul>
<li>左の「handles」は<strong>外部参照を保持する変数</strong>の一覧です。ここをクリックするとその参照を切れます( = オブジェクトが他で参照されていない状態をシミュレート)。</li>
<li>実際の WeakMap / WeakSet は列挙できないため、可視化用に <code>visualMap</code> / <code>visualSet</code> を別に持ち、"GC実行" でそれらを消します。</li>
</ul>
</div>
</div>
<!-- 可視化パネル -->
<div class="panel">
<h2>可視化(visualMap / visualSet)</h2>
<div style="display:flex; gap:8px; align-items:center; margin-bottom:8px;">
<input id="vmKey" placeholder="キー (for Map)" />
<input id="vmVal" placeholder="値 (for Map)" />
<button id="vmAdd">visualMap に set()</button>
</div>
<div style="display:flex; gap:8px; align-items:center; margin-bottom:8px;">
<input id="vsVal" placeholder="値 (for Set)" />
<button id="vsAdd">visualSet に add()</button>
</div>
<div style="display:flex; gap:12px;">
<div style="flex:1">
<div class="muted">visualMap(可視化用・列挙可) <span class="badge">Map</span></div>
<div id="visualMapList" class="list"></div>
</div>
<div style="flex:1">
<div class="muted">visualSet(可視化用・列挙可) <span class="badge">Set</span></div>
<div id="visualSetList" class="list"></div>
</div>
</div>
<div style="margin-top:8px;">
<strong>実際の WeakMap / WeakSet (非列挙)</strong>
<div class="muted">※内部は列挙不可のため表示できません。操作の様子はログで確認できます。</div>
<div class="log" id="actionLog"></div>
</div>
</div>
</div>
<div class="note">
<strong>重要な注意:</strong>
<ul>
<li>本デモでは <code>weakMap</code> / <code>weakSet</code> に実際にオブジェクトを格納しますが、JS 実行環境がオブジェクトを GC するかどうかは制御できません。</li>
<li>そのため UI 上は <code>visualMap</code> / <code>visualSet</code> を「GC発生で削除される」ものとして扱い、ユーザーが「参照を切る」→「シミュレーションGC」を押すと視覚的に消えます。</li>
</ul>
</div>
<script>
/*
実際の WeakMap / WeakSet と、可視化用 Map / Set を並行して扱う。
- handles[]: 外部参照を保持する配列。null にすると参照を切ったことになる(シミュレーション)。
- weakMap / weakSet: 実際にオブジェクトを格納(ただし列挙不可)。
- visualMap / visualSet: 可視化用にキー/値を保持し、"simulateGC()" で外部参照が切れているものを削除する。
*/
const handles = []; // {id, name, obj}
const weakMap = new WeakMap();
const weakSet = new WeakSet();
const visualMap = new Map(); // keyStr -> {keyRefId, value}
const visualSet = new Map(); // store value -> {refId}
const handlesEl = document.getElementById('handles');
const visualMapList = document.getElementById('visualMapList');
const visualSetList = document.getElementById('visualSetList');
const actionLog = document.getElementById('actionLog');
function log(msg) {
const now = new Date().toLocaleTimeString();
actionLog.innerHTML = `[${now}] ${msg}<br>` + actionLog.innerHTML;
}
// オブジェクト作成(外部参照を保持)
document.getElementById('create').addEventListener('click', () => {
const name = document.getElementById('objName').value.trim() || `obj${handles.length+1}`;
// 実際のオブジェクト(ここに何でも入れられる)
const obj = { __demoId: handles.length+1, name };
const id = handles.length;
handles.push({ id, name, obj });
renderHandles();
log(`Created external ref "${name}" (id=${id})`);
document.getElementById('objName').value = '';
});
// handles 表示(クリックで参照を切る/復元する)
function renderHandles(){
handlesEl.innerHTML = '';
handles.forEach(h => {
const div = document.createElement('div');
div.className = 'entry';
const left = document.createElement('div');
left.innerHTML = `<strong>${h.name}</strong> <div class="muted">id:${h.id}</div>`;
const btn = document.createElement('button');
btn.textContent = h.obj ? '参照を切る (drop)' : '参照を復元 (keep)';
btn.style.background = h.obj ? '#d9534f' : '#28a745';
btn.addEventListener('click', () => {
if (h.obj) {
// 参照を切る(シミュレーション)
h.obj = null;
log(`User dropped external reference for id=${h.id}`);
} else {
// 復元(実際は新しいオブジェクトを作り直す)
const newObj = { __demoId: h.id, name: h.name + '_restored' };
h.obj = newObj;
log(`User restored external reference for id=${h.id}`);
}
renderHandles();
});
div.appendChild(left);
div.appendChild(btn);
handlesEl.appendChild(div);
});
}
// visualMap に set(キーは外部参照の id を使う or 任意文字列)
document.getElementById('vmAdd').addEventListener('click', () => {
const keyText = document.getElementById('vmKey').value.trim();
const val = document.getElementById('vmVal').value.trim() || '(empty)';
if (!keyText) return;
// キーとして handles の id を指定できる(例: "h:0")
let keyRef = null;
const m = keyText.match(/^h:(\d+)$/);
if (m) {
const id = Number(m[1]);
const h = handles[id];
if (!h) { alert('そのidのハンドルがありません'); return; }
if (!h.obj) { alert('そのハンドルは参照切り状態です:先に復元するか別キーを使ってください'); return; }
keyRef = h.obj;
} else {
// 文字列キー:文字列をそのままキーにする(可視化用)
keyRef = keyText;
}
// 実際に WeakMap に set(キーがオブジェクトのときのみ)
if (typeof keyRef === 'object') {
weakMap.set(keyRef, val);
log(`weakMap.set(objectKey, "${val}")`);
} else {
// object でないキーは WeakMap に入れられない -> skip real weakMap
log(`(note) non-object key; real WeakMap cannot store primitive key. Only visualMap updated.`);
}
// visualMap 用にはキーの文字列化を使って表示管理
const displayKey = (typeof keyRef === 'object') ? `h:${keyRef.__demoId}` : String(keyRef);
visualMap.set(displayKey, { keyRefId: (typeof keyRef === 'object' ? keyRef.__demoId : null), value: val });
renderVisualMap();
document.getElementById('vmKey').value = '';
document.getElementById('vmVal').value = '';
});
// visualSet に add(値は任意。外部参照ハンドルをrefIdとして持てる)
document.getElementById('vsAdd').addEventListener('click', () => {
const v = document.getElementById('vsVal').value.trim();
if (!v) return;
// 値として handles のハンドル参照を使う記法: "h:0"
const m = v.match(/^h:(\d+)$/);
if (m) {
const id = Number(m[1]);
const h = handles[id];
if (!h) { alert('そのidのハンドルがありません'); return; }
if (!h.obj) { alert('そのハンドルは参照切り状態です:先に復元するか別値を使ってください'); return; }
weakSet.add(h.obj);
visualSet.set(`h:${id}`, { refId: id });
log(`weakSet.add(object from handle h:${id})`);
} else {
// primitive 値は WeakSet に入れられない -> only visualSet
visualSet.set(v, { refId: null });
log(`visualSet.add("${v}") (primitive values not allowed in real WeakSet)`);
}
renderVisualSet();
document.getElementById('vsVal').value = '';
});
function renderVisualMap(){
visualMapList.innerHTML = '';
for (const [k, v] of visualMap.entries()) {
const div = document.createElement('div');
div.className = 'entry';
const left = document.createElement('div');
left.innerHTML = `<strong>${k}</strong><div class="muted">${v.value}</div>`;
const btnWrap = document.createElement('div');
const btn = document.createElement('button');
btn.textContent = '削除 (visual)';
btn.addEventListener('click', () => {
visualMap.delete(k);
renderVisualMap();
log(`visualMap: removed ${k}`);
});
btnWrap.appendChild(btn);
div.appendChild(left);
div.appendChild(btnWrap);
visualMapList.appendChild(div);
}
}
function renderVisualSet(){
visualSetList.innerHTML = '';
for (const [k, v] of visualSet.entries()) {
const div = document.createElement('div');
div.className = 'entry';
const left = document.createElement('div');
left.innerHTML = `<strong>${k}</strong>`;
const btn = document.createElement('button');
btn.textContent = '削除 (visual)';
btn.addEventListener('click', () => {
visualSet.delete(k);
renderVisualSet();
log(`visualSet: removed ${k}`);
});
div.appendChild(left);
div.appendChild(btn);
visualSetList.appendChild(div);
}
}
// ユーザー操作:すべての外部参照を切る(handles の obj を null にする)
document.getElementById('dropAll').addEventListener('click', () => {
handles.forEach(h => { h.obj = null; });
renderHandles();
log('User dropped ALL external references (simulated)');
});
// シミュレーションGC:参照が切れた handles に対応する visualMap/visualSet のエントリを削除
document.getElementById('runSimGC').addEventListener('click', () => {
// visualMap のうち、keyRefId を持つものは handles[id].obj が null なら削除対象
for (const [k, v] of Array.from(visualMap.entries())) {
if (v.keyRefId != null) {
const h = handles[v.keyRefId];
if (!h || !h.obj) {
visualMap.delete(k);
log(`SimGC: visualMap entry ${k} removed (no external refs)`);
}
}
}
// visualSet similarly
for (const [k, v] of Array.from(visualSet.entries())) {
if (v.refId != null) {
const h = handles[v.refId];
if (!h || !h.obj) {
visualSet.delete(k);
log(`SimGC: visualSet entry ${k} removed (no external refs)`);
}
}
}
renderVisualMap();
renderVisualSet();
log('Simulated GC completed (visualMap/visualSet updated).');
});
</script>
</body>
</html>
HTML使い方(短く)
- オブジェクトを作成(
オブジェクト作成)。作成するとhandlesに外部参照を保持します。 visualMap/visualSetに要素を追加(キーにh:0のようにハンドル指定するとそのオブジェクトをキー/値として使う)。- ハンドルのボタンで 参照を切る(drop) すると、そのオブジェクトは「外部参照を持たない状態」をシミュレート。
シミュレートされたGCを実行を押すと、参照が切れた項目が visualMap / visualSet から消えます(これが「GCで自動削除されるイメージ」の視覚化です)。- 実際の
WeakMap/WeakSetにはすでに対応するset/addを行っているログも残りますが、実際にブラウザが GC を走らせるかどうかは別問題です。
⚠️ 技術的な注意(改めて)
- 実際の WeakMap / WeakSet は列挙不可なので、要素の一覧表示はできません(だから
visualMap/visualSetを別に持っています)。 - 実際の GC 発生は JS エンジン任せで、当デモが実際の削除を保証するわけではありません。デモは「概念(参照が切れる → 最終的に消える)」を直感的に学ぶための教材です。


