「instanceof」はなぜ裏切るのか?プロトタイプチェーンの深淵を覗く
現場でコードレビューをしていると、`typeof` で判定できない複雑なオブジェクトを扱う際に、安易に `instanceof` を使って「型チェック」をしているコードに出くわすことがある。
「これ、本当に安全か?」
そう尋ねると、多くのエンジニアは「え、クラスのインスタンスかどうか確認するだけじゃないんですか?」と答える。間違いではない。だが、`instanceof` が「何を」見ているのかを理解せずに使うのは、目隠しをして高速道路を走るようなものだ。今回は、JavaScriptのプロトタイプチェーンの裏側と、実務で痛い目を見ないための `instanceof` の真実を紐解いていこう。
—
1. `instanceof` が裏側で行っている「泥臭い」仕事
`object instanceof Constructor` と書いたとき、JavaScriptエンジンは裏で何をしているのか。実はこれ、極めてシンプルかつ泥臭いループ処理を行っている。
端的に言えば、「右側のコンストラクタの `prototype` プロパティが、左側のオブジェクトのプロトタイプチェーン上のどこかに存在するか?」 を走査しているだけだ。
具体的には、以下の手順を繰り返している。
1. ターゲットオブジェクトの `[[Prototype]]`(`__proto__`)を取得する。
2. そのプロトタイプが、コンストラクタの `prototype` プロパティと一致するか確認する。
3. 一致しなければ、プロトタイプを一段階上(親)に辿り、2に戻る。
4. `null` に到達するまで繰り返しても見つからなければ `false` を返す。
つまり、`instanceof` はクラスの静的な型チェックではなく、「プロトタイプチェーンの探索アルゴリズム」なのだ。
—
2. 実務で遭遇する「嘘」と「落とし穴」
この仕様を知っていると、なぜ `instanceof` が予期せぬ挙動をするのかが見えてくる。特にフロントエンド開発でよく踏む地雷が「複数ウィンドウ(iframe)」の問題だ。
iframeの罠
異なるフレームやウィンドウで生成されたオブジェクトは、実はそれぞれ異なるグローバル環境(Realm)に属している。つまり、`Array` や `Object` のコンストラクタ自体が別物なのだ。
// iframe内のArrayを親ウィンドウで受け取った場合
const iframeArray = iframe.contentWindow.Array([]);
console.log(iframeArray instanceof Array); // -> false
「配列なのに `instanceof Array` が `false` になる」。これが、現場で `instanceof` を過信してはいけない最大の理由だ。
—
3. 実践:安全な型判定の実装
では、どうすればいいのか? 実務においては、`Object.prototype.toString` を利用した「タグチェック」を併用するのが、最も堅牢でプロのやり方だ。
以下に、現場でそのまま使える、堅実な型判定ユーティリティのサンプルを置いておく。
/
- 堅牢な型判定ユーティリティ
- instanceofの弱点を補い、信頼性を担保する
/
const isArray = (value) => {
// モダン環境なら Array.isArray が最強だが、概念理解のために
return Object.prototype.toString.call(value) === ‘[object Array]’;
};
class CustomTask {}
const task = new CustomTask();
// 1. instanceofは「同一Realm内の継承関係」を確認するのに適している
console.log(task instanceof CustomTask); // -> true
// 2. 外部からのデータや、素性が怪しいオブジェクトには
// 「プロトタイプを偽装されたら終わり」というリスクがあることを忘れないこと
// 以下の手法は、__proto__を書き換えられると突破される
const fake = { __proto__: CustomTask.prototype };
console.log(fake instanceof CustomTask); // -> true (本物ではないのにtrueになる!)
—
4. シニアアーキテクトからの提言
私が後輩によく言うのは、「`instanceof` は『クラスの継承関係』を検証するために使い、データの『型そのもの』を保証するために使ってはいけない」ということだ。
- ライブラリの内部実装: ユーザーから渡されたオブジェクトが、特定のクラスのメソッドを持っているか保証したい → `instanceof` はアリ。
- APIレスポンスの検証: サーバーから来たJSONが配列かどうか判定したい → `Array.isArray()` を使う(`instanceof` は絶対NG)。
- DOM要素の判定: `instanceof HTMLElement` は、異なるドキュメント間では失敗する可能性があるため慎重に。
JavaScriptの仕様は、ある意味で「柔軟すぎる」言語だ。だからこそ、我々エンジニアがその仕様の裏にある「走査の仕組み」を正しく理解し、ケースバイケースで最適な道具を選ぶ必要がある。
「なんとなく動く」から「なぜ動くのか説明できる」へ。その一歩先が、あなたの書くコードをより堅牢で、他の誰が見ても安心できるものに変えてくれるはずだ。
次は、`Symbol.hasInstance` を使って、`instanceof` の判定ロジックをあえてカスタマイズする高度なテクニックについても触れてみたい。興味があれば、ぜひ自分の環境で `console.log` を叩きまくってみてほしい。泥臭い検証こそが、一番の近道だ。

コメント