TypeScriptでアプリケーションを開発する際、オブジェクトが特定のクラスのインスタンスであるかどうかを判定したい場面が頻繁に発生します。
JavaScript標準の演算子である instanceof は、TypeScriptにおいても非常に重要な役割を果たしており、単なる判定以上の機能を提供します。
TypeScriptのコンパイラは instanceof を「型ガード(Type Guard)」として認識し、条件分岐の中での型を自動的に絞り込んでくれます。
本記事では、instanceof の基本的な使い方から、プロトタイプチェーンに基づいた内部の仕組み、さらには実戦で役立つ応用パターンまでを詳しく解説します。
安全なコードを書くための必須知識として、instanceof の挙動を深く理解していきましょう。
TypeScriptにおけるinstanceofの基本
instanceof演算子の構文と基本的な動作
instanceof 演算子は、あるオブジェクトが特定のクラス(あるいはその派生クラス)から生成されたインスタンスかどうかを判定するために使用されます。
基本的な構文は object instanceof Constructor という形式で記述し、結果は真偽値(boolean)で返されます。
以下のコードは、最もシンプルな instanceof の使用例を示しています。
class Animal {
move() {
console.log("Moving...");
}
}
class Dog extends Animal {
bark() {
console.log("Woof!");
}
}
const myDog = new Dog();
// Dogのインスタンスであるか判定
console.log(myDog instanceof Dog);
// 親クラスであるAnimalのインスタンスでもある
console.log(myDog instanceof Animal);
// 無関係なクラスとの比較
console.log(myDog instanceof Object);
true
true
true
このように、継承関係にあるクラスに対しても true を返すのが instanceof の大きな特徴です。
これは、JavaScriptのプロトタイプチェーンを遡ってチェックを行っているためです。
TypeScriptにおける型推論への影響
TypeScriptにおいて instanceof を使用する最大のメリットは、静的な型推論の強化にあります。
通常、複数の型が含まれる「ユニオン型」の変数を扱う場合、そのままでは特定の型に固有のメソッドを呼び出すことはできません。
しかし、instanceof でチェックを行うことで、TypeScriptコンパイラはそのブロック内での型を特定してくれます。
function announce(pet: Dog | Animal) {
if (pet instanceof Dog) {
// この中では pet は Dog 型として扱われる
pet.bark();
} else {
// ここでは Dog ではない Animal 型として扱われる
pet.move();
}
}
このように、実行時の判定と静的な型チェックをシームレスに同期させることが可能になります。
これをコントロールフロー分析と呼び、バグの混入を防ぐ強力な武器となります。
instanceofによる型ガードの仕組み
型ガード(Type Guard)とは何か
型ガードとは、特定のスコープ内において変数の型をより具体的なものに絞り込むための仕組みです。
TypeScriptにはいくつかの型ガードが存在しますが、instanceof はその中でも「クラス」を対象とした最も一般的な方法です。
typeof 演算子も型ガードとして機能しますが、こちらは string や number などのプリミティブ型の判定に適しています。
一方で、ユーザー定義のクラスや、組み込みの Date, RegExp などのオブジェクトを判定する場合には instanceof が必須となります。
ユニオン型との組み合わせ
大規模なアプリケーションでは、一つの引数に異なる種類のクラスが渡される設計がよく見られます。
例えば、ファイルアップロード機能で File オブジェクトか Blob オブジェクトのどちらかを受け取るような場合です。
function processInput(input: File | Blob) {
if (input instanceof File) {
console.log(`Processing file: ${input.name}`);
} else {
console.log(`Processing blob of size: ${input.size}`);
}
}
上記のコードでは、File クラスが持つ name プロパティにアクセスする前に、instanceof で安全性を確保しています。
もしこのチェックを怠ると、TypeScriptはコンパイルエラーを発生させ、実行時のランタイムエラーを未然に防いでくれます。
このように、instanceof は「型安全な条件分岐」を実現するための基盤と言えるでしょう。
クラスとインターフェースの決定的な違い
インターフェースにinstanceofが使えない理由
TypeScriptを使い始めたばかりの方が陥りやすい罠として、interface に対して instanceof を使おうとするケースがあります。
結論から述べると、interfaceに対してinstanceofを使用することはできません。
これは、TypeScriptの interface がコンパイル時に完全に消去され、JavaScriptの実行時には存在しないためです。
以下のコードはコンパイルエラーになります。
interface Point {
x: number;
y: number;
}
function checkPoint(p: any) {
// コンパイルエラー: 'Point' only refers to a type, but is being used as a value here.
if (p instanceof Point) {
console.log(p.x);
}
}
instanceof の右辺には、実行時に実体として存在する「コンストラクタ関数(クラス)」を置く必要があります。
実行時に情報が残るクラスの利点
一方、class はTypeScriptで定義しても、コンパイル後にJavaScriptの関数(またはクラス構文)として残ります。
そのため、実行時の型判定が必要な場合は、interface ではなく class を選択するのが適切な設計となります。
以下の表で、interface と class の違いを整理しました。
| 機能 | Interface | Class |
|---|---|---|
| コンパイル後の実体 | 消失する | 残る |
| instanceofでの判定 | 不可 | 可能 |
| メモリ消費 | ゼロ | わずかに発生 |
| 用途 | 構造の定義のみ | 実装と実体の保持 |
実行時の振る舞いを制御したいのか、あるいはコード上の契約を定義したいだけなのかによって、これらを使い分けることが重要です。
プロトタイプチェーンとinstanceofの内部挙動
JavaScriptの継承メカニズム
instanceof がどのようにして判定を行っているかを理解するには、JavaScriptのプロトタイプ継承を知る必要があります。
instanceof は、オブジェクトの内部的なプロトタイプ参照(__proto__)を順に辿ります。
そして、右辺に指定したコンストラクタの prototype プロパティが、そのチェーンのどこかに出現するかどうかを確認します。
この仕組みがあるため、多階層の継承が行われていても正しく判定を行うことができるのです。
Symbol.hasInstanceによる動作のカスタマイズ
2026年現在のモダンなJavaScript/TypeScript環境では、instanceof の挙動を開発者がカスタマイズすることも可能です。
これには Symbol.hasInstance という特殊なシンボルを使用します。
通常、instanceof はプロトタイプをチェックしますが、このメソッドをクラスに実装することで、独自の判定ロジックを注入できます。
class SpecialCheck {
static [Symbol.hasInstance](instance: any) {
// 特殊な条件:'magic'というプロパティを持っていればインスタンスとみなす
return instance && typeof instance === 'object' && 'magic' in instance;
}
}
const obj = { magic: true };
console.log(obj instanceof SpecialCheck);
true
この機能は非常に強力ですが、標準的な instanceof の期待される挙動(プロトタイプの一致)を破壊する可能性があるため、使用には注意が必要です。
ライブラリ開発などで、特殊な透過的オブジェクトを扱う場合に限定して検討するのが良いでしょう。
実践的な活用シーン:独自エラークラスの判定
エラーハンドリングにおける有用性
instanceof の最も実用的なユースケースの一つが、カスタムエラー(独自例外)の判定です。
APIエラー、認証エラー、バリデーションエラーなど、エラーの種類によって後続の処理を分けたい場合に非常に便利です。
class ApiError extends Error {
constructor(public statusCode: number, message: string) {
super(message);
this.name = "ApiError";
}
}
function fetchData() {
try {
// 何らかの処理
throw new ApiError(404, "Not Found");
} catch (error) {
if (error instanceof ApiError) {
// ApiError特有のプロパティ statusCode にアクセス可能
console.error(`API Error: ${error.statusCode} - ${error.message}`);
} else if (error instanceof Error) {
// 標準のErrorの場合
console.error(`Standard Error: ${error.message}`);
} else {
// error が object ではない可能性も考慮
console.error("Unknown error occurred");
}
}
}
このように catch ブロック内で instanceof を使うことで、安全にエラー詳細を取得できます。
TypeScript 4.4以降、catch(error) の error はデフォルトで unknown 型となるため、このような型ガードによるチェックは事実上必須となっています。
注意点:ES5ターゲット時のプロトタイプ修正
古い環境をターゲット(ES5)にしてコンパイルする場合、Error クラスを継承した際に instanceof が正しく動作しないという有名な問題があります。
これは、ES5の組み込みオブジェクトの継承における制限によるものです。
この問題を回避するためには、コンストラクタ内で明示的にプロトタイプを設定し直す必要があります。
class CustomError extends Error {
constructor(m: string) {
super(m);
// ES5以下をターゲットにする場合に必要
Object.setPrototypeOf(this, CustomError.prototype);
}
}
モダンな環境(ES6以降)であればこの記述は不要ですが、プロジェクトの tsconfig.json の target 設定を確認しておくことが大切です。
instanceofを使用する際の注意点と限界
異なる実行コンテキスト(iframe等)での不具合
instanceof には、ブラウザ環境特有の落とし穴が存在します。
それは、iframeや別ウィンドウから渡されたオブジェクトの判定に失敗するという問題です。
それぞれのウィンドウ(実行コンテキスト)は独自のグローバルオブジェクトを持っており、それぞれの Array や Object クラスが存在します。
そのため、iframe内で作成された [] を親ウィンドウで instanceof Array と判定すると、false になってしまいます。
このようなケースでは、Array.isArray() などの専用メソッドや、構造に基づく判定を検討する必要があります。
構造的部分型と名目的な判定の乖離
TypeScriptは基本的に「構造的部分型(Structural Typing)」を採用しています。
これは、プロパティの形が同じであれば同じ型とみなす考え方です。
しかし、instanceof は「名目的な判定(Nominal check)」、つまりどのコンストラクタで作られたかを厳密にチェックします。
以下の例を見てみましょう。
class Person {
name: string = "unknown";
}
class Robot {
name: string = "unit-01";
}
const r = new Robot();
// TypeScriptの型システム上は Person 型として代入可能
const p: Person = r;
// しかし、instanceof は実体をチェックするため false になる
console.log(p instanceof Person);
false
型定義上は互換性があっても、instanceof の結果は期待と異なる場合があります。
「見た目が同じならOK」というTypeScriptの基本方針と、instanceofの挙動は異なるという点を強く意識しておきましょう。
比較表:typeof、instanceof、ユーザー定義型ガード
最後に、型判定によく使われる手法の違いを表にまとめました。
| 手法 | 判定対象 | 主な用途 | 実行時の実体 |
|---|---|---|---|
typeof | プリミティブ型 | string, number, boolean等の判定 | JavaScript標準機能 |
instanceof | クラス・インスタンス | 自作クラスやDate等の判定 | JavaScript標準機能 |
is (Type Predicates) | 任意の条件 | interfaceや複雑な構造の判定 | TypeScript独自の関数 |
in | プロパティの有無 | 特定の鍵を持つオブジェクトの判定 | JavaScript標準機能 |
用途に応じて最適な手法を選択することが、メンテナンス性の高いコードへの近道です。
まとめ
TypeScriptにおける instanceof は、実行時のオブジェクト判定と静的な型安全性を結びつける強力なツールです。
特に、クラスベースの設計やエラーハンドリングにおいては欠かせない存在と言えます。
一方で、インターフェースには使用できない、プロトタイプチェーンに依存するといった JavaScript 由来の特性も併せ持っています。
これらの特性を正しく理解し、instanceof を適切に使いこなすことで、実行時エラーに強い堅牢なアプリケーションを構築することができます。
本記事で紹介した型ガードの仕組みや注意点を、日々の開発にぜひ活かしてみてください。
