WeakMapとWeakSetの「正体」を暴く:メモリリークを防ぐための型判定術
現場でコードを書いていて、「これってWeakMapだっけ?普通のMapだっけ?」と混乱したことはないだろうか。
フロントエンドのパフォーマンスチューニングにおいて、`WeakMap`と`WeakSet`はメモリ管理の切り札だ。しかし、これらは「特殊なオブジェクト」であるがゆえに、安易な判定手法では足元をすくわれることがある。今日は、JavaScriptの深淵を覗きつつ、現場で確実に使える型判定の作法を伝授しよう。
—
なぜWeakMap/WeakSetの判定は難しいのか
まず前提として、`typeof`演算子に頼るのはやめよう。`typeof`はこれらを単なる`”object”`としてしか認識しない。これでは「それがMapなのかWeakMapなのか」を区別できない。
また、`instanceof`を使えば一見解決するように思えるが、実は落とし穴がある。iframeを跨いだコンテキストや、複数の実行環境(Realm)が混在する複雑なアプリケーションでは、プロトタイプチェーンが分断され、`instanceof`が`false`を返すケースがあるのだ。
では、プロはどう判断しているのか?
結論:Object.prototype.toString.call を信じろ
JavaScriptの標準仕様において、組み込みオブジェクトの型を最も堅牢に判定する方法は、`Object.prototype.toString`を直接呼び出すことだ。いわゆる「[object Type]」という文字列を得る手法である。
これを使うと、エンジン内部の `[[Class]]` 内部スロットを直接覗きに行くことができるため、コンテキストの境界を越えて正確に判定できる。
実践的な判定関数のコード
以下は、現場でそのままコピペして使える、堅牢な判定ロジックだ。
/
- 高精度な型判定ユーティリティ
- WeakMapやWeakSetを正確に判別します
/
const isWeakMap = (value) => {
// toString.callを使い、[object WeakMap]という文字列を確認する
return Object.prototype.toString.call(value) === ‘[object WeakMap]’;
};
const isWeakSet = (value) => {
// 同様に[object WeakSet]を確認
return Object.prototype.toString.call(value) === ‘[object WeakSet]’;
};
// — テストコード —
const wm = new WeakMap();
const ws = new WeakSet();
const map = new Map();
console.log(isWeakMap(wm)); // true
console.log(isWeakMap(map)); // false
console.log(isWeakSet(ws)); // true
// ちなみに、typeofはどちらも “object” を返してしまい役に立たない
console.log(typeof wm); // “object”
—
ブラウザの裏側で起きていること
なぜ`WeakMap`は特殊なのか。それは、「キーとなるオブジェクトが参照されなくなったら、そのエントリごとガベージコレクション(GC)対象にする」という特殊な契約がエンジンレベルで結ばれているからだ。
そのため、普通の`Map`と違い、`WeakMap`は「列挙不可能(Non-enumerable)」だ。`for…of`で回すことも、`.size`プロパティを確認することもできない。これは仕様上、いつGCが走るか予測できないため、サイズを確定させることが物理的に不可能だからだ。
もしあなたがデバッグ中に `console.log` で中身が見えないと焦ったとしても、それは「ブラウザが壊れている」のではなく、「それがWeakMapの正常な振る舞い」なのだと理解してほしい。
実務での「守り」としての考え方
現場のコードでこれらの判定を行うシーンは、主に以下の2つだろう。
1. ライブラリやユーティリティの引数チェック: 外部から渡されたデータが「キャッシュ用オブジェクト(WeakMap)」なのか「通常のデータ」なのかを判別し、適切な処理に振り分けたいとき。
2. デバッグ・ロギング: メモリリーク調査の際、ヒープスナップショットではなく、実行中のオブジェクトが何の型かをログ出力してトレースしたいとき。
正直に言おう。頻繁に`isWeakMap`を呼び出すような設計になっているなら、それは設計を見直すサインかもしれない。`WeakMap`は「外から隠蔽して保持するもの」であり、その性質上、外部から判定する必要性があまりないはずだからだ。
最後に:シニアからのアドバイス
「型判定」は強力なツールだが、多用しすぎるとコードの柔軟性を損なう。「型を判定して分岐する」のではなく、「ポリモーフィズムで解決できないか?」を常に自問自答してほしい。
それでも、JavaScriptというカオスな言語で開発を続ける以上、今回紹介した`toString.call`という「最後の砦」だけは、必ず記憶の引き出しの奥深くに入れておいてくれ。いざという時、君のコードをバグから救ってくれるはずだ。
次は、もし君が余裕があれば、「Proxyを用いたWeakMapのハック的な拡張」に挑戦してみると面白い。JavaScriptの深淵はまだまだ深いぞ。精進してくれ。

コメント