Menu

TypeScriptのinstanceof: クラスとErrorの型の絞り込み

instanceof は、オブジェクトがクラスから作られたかを実行時にチェックし、TypeScriptは変数をそのクラスに絞り込みます。自作クラスと Error のサブクラスでの使い方、インターフェースや型エイリアスをチェックできない理由、意外な結果になる場面を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

value instanceof SomeClass は実行時に動くJavaScriptのチェックです。オブジェクトのプロトタイプチェーンに SomeClass.prototype があるとき、つまりオブジェクトが new SomeClass(またはサブクラス)で作られたときに true になります。チェックの中では、TypeScriptが value を SomeClass に絞り込みます。

クラスから作ったオブジェクトには instanceof を、プリミティブには typeof を使います。この例では両方が必要です。instanceof で Date を分け、残りを typeof で分けています。

自作クラスの絞り込み

instanceof は true のブランチではそのクラスに絞り込み、false のブランチではそのクラスを取り除くので、クラスのユニオン型をメンバーごとに処理できます。

サブクラスのインスタンスは親クラスのチェックも通ります。Square extends Rect なら、new Square(2) instanceof Rect は true です。ブランチの処理が違う場合は、いちばん具体的なクラスを先にチェックしましょう。

catch の中の Error サブクラス

instanceof がいちばんよく使われるのは catch の中です。strict のもとでは捕捉した値は unknown(何でも throw できるため)で、型付きのエラーに戻すには instanceof を使います。

出力:

404: /missing.txt
TypeError: path must be absolute

class X extends Error は、TypeScript 7 がサポートするすべてのターゲット(ES2015 以降)で instanceof と一緒に使えます。コンストラクターで Object.setPrototypeOf(this, X.prototype) を呼ぶという古いアドバイスは、ES5 向けにコンパイルしたコードのためのもので、TypeScript 7 はそのターゲットを削除しています。

instanceof はインターフェースや型には使えない

インターフェースと型エイリアスはコンパイラーのためだけに存在します。コンパイル後には比較する User という値がないので、TypeScriptはこのチェックを拒否します:

コンパイラーは index.ts(8,24): error TS2693: 'User' only refers to a type, but is being used as a value here. を報告します。直し方は2つあります。1つは、value is User を返す関数、つまり型ガードで形を自分でチェックする方法です:

もう1つは、自分のコードでこれらのオブジェクトを作るなら、User をクラスにして new で作る方法です。そうすれば instanceof が使えます。型述語とアサーション関数については 型ガード のページで詳しく説明しています。

型が正しくてもインスタンスとは限らない

TypeScriptは型を構造で比較します。クラスと同じメンバーを持つオブジェクトリテラルは、そのクラスの型に代入できます。instanceof は構造を見ません。プロトタイプチェーンをたどるので、new で作られていないオブジェクトは、コンパイラーがその型として受け入れていてもチェックに失敗します。

最後の行が重要です。structuredClone、JSON.parse(JSON.stringify(...))、ワーカー間のメッセージは、静的な型が Point のままでも、クラスのプロトタイプを持たない普通のオブジェクトを返します。クラスのインスタンスがこうした境界を越えるときは、instanceof やメソッドに頼る前に作り直しましょう(new Point(copy.x, copy.y))。

instanceof とプリミティブ

プリミティブ(string、number、boolean など)はオブジェクトではなく、プロトタイプチェーンを持たないので、"hi" instanceof String は false です。TypeScriptは可能な場合この間違いを指摘します。左辺が string 型の値だと、instanceof はエラー TS2358 The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter. になります。プリミティブには typeof を使いましょう。

値instanceof のチェック結果
new Date()instanceof Datetrue
[1, 2]instanceof Arraytrue(ただし Array.isArray を使うほうがよい)
new TypeError("x")instanceof Errortrue(サブクラス)
{ x: 1, y: 2 }instanceof Pointfalse(コンストラクターで作られていない)
Object.create(null)instanceof Objectfalse(プロトタイプがない)
"hi"instanceof Stringfalse(プリミティブ)

別のレルムの値

instanceof は特定の1つのコンストラクターオブジェクトと比較します。別のレルム(iframe や Node の vm コンテキスト)で動くコードは独自の Array、Error、Date を持っているので、そこで作った配列はこちらの instanceof Array に失敗します。同じ npm パッケージのコピーが node_modules に2つ入った場合も同じです。コピーごとにクラスが別なので、一方のインスタンスはもう一方に対する instanceof に失敗します。配列なら Array.isArray はレルムをまたいでも動きます。自作の型なら、プロパティをチェックする方法(型ガードや kind フィールド)でこの問題を完全に避けられます。

よくある質問

TypeScriptでオブジェクトがクラスのインスタンスかどうかをチェックするには?

value instanceof ClassName を使います。実行時のチェック(普通のJavaScript)で、if の中ではTypeScriptが value を ClassName に絞り込みます。Date、Map、Error のような組み込みクラスにも自作のクラスにも使えます。

TypeScriptでインターフェースに instanceof を使えますか?

使えません。インターフェースと型エイリアスはJavaScriptへのコンパイル時に消去されるので、実行時にチェックする対象がありません。インターフェース User に対する x instanceof User はエラー TS2693「'User' only refers to a type, but is being used as a value here.」になります。代わりに型ガード関数でプロパティをチェックするか、オブジェクトを自分で作るなら User をクラスにします。

型は正しいのに instanceof が false を返すのはなぜですか?

TypeScriptの型は構造的です。オブジェクトリテラル { x: 1, y: 2 } は、同じメンバーを持っていれば Point クラスの型に代入できます。しかし instanceof はプロトタイプチェーンをチェックし、このリテラルは new Point で作られていないので false になります。JSON、structuredClone、メッセージチャネルを通ったクラスのインスタンスも、普通のオブジェクトになって戻ってくるので同じことが起きます。

TypeScriptで自作の Error クラスに instanceof は使えますか?

はい、モダンなターゲットならどれでも使えます。class NotFound extends Error {} と書けば、err instanceof NotFound は true になります。false を返してしまう昔の問題はES5向けにコンパイルした出力だけのもので、TypeScript 7 は ES5 ターゲットをサポートしていません。

"hello" instanceof String が false になるのはなぜですか?

文字列リテラルはオブジェクトではなくプリミティブなので、チェックするプロトタイプチェーンがありません。instanceof String が true になるのは、new String() で作ったラッパーオブジェクトだけです。TypeScriptは string 型の値への instanceof を拒否します(TS2358)。プリミティブには typeof value === "string" を使います。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める