typeof演算子の使い方|JavaScriptの型判定とTypeScriptのtypeof型・keyof型
typeofは同じ綴りで2つの別物を指します。JavaScriptのtypeofは値を受け取り、その型名を文字列で返す実行時の演算子です。一方TypeScriptのtypeofは値から型を取り出す型レベルの演算子で、動きも使う場所もまったく違います。この記事では、JavaScriptのtypeofによる型判定と落とし穴、TypeScriptのtypeof型・keyof型、そして両者を組み合わせるkeyof typeofイディオムまでを、実行結果つきのコード例で整理します。
まとめ:typeofの要点(先に結論)
- JavaScriptのtypeofは値を受け取り、
"string""number"など8種類の型名を文字列で返す(実行時)。 typeof nullは"object"、配列も"object"を返す。配列判定はArray.isArray()、その他の内部型はObject.prototype.toString.call()を使う。- 未宣言の変数に対してもtypeofはエラーを投げないため、存在チェックに使える。
- TypeScriptのtypeofは型を書く位置で使い、
type T = typeof objのように値から型を生成する(コンパイル時)。 - keyofは型を受け取り、そのプロパティ名のユニオン型を作る。
keyof typeof objで「実行時のオブジェクトのキー集合」を型として得るのが定番。
JavaScriptのtypeof演算子とは(値の型を文字列で返す)
JavaScriptのtypeofは、直後に置いた値の型を小文字の文字列で返す単項演算子です。書き方はtypeof xとtypeof(x)のどちらでも同じ結果になります。返り値は必ず文字列なので、typeof x === "number"のように等価比較で分岐に使います。
typeof "hello"; // "string"
typeof 42; // "number"
typeof true; // "boolean"
typeof undefined; // "undefined"
typeof 10n; // "bigint"
typeof Symbol(); // "symbol"
typeof {}; // "object"
typeof function(){}; // "function"
typeofが返す8種類の型名
typeofの返り値は次の8つに限られます。プリミティブ(string/number/bigint/boolean/undefined/symbol)はそれぞれ固有の文字列を返し、関数は"function"、それ以外のオブジェクトはすべて"object"にまとめられます。
| 値の例 | typeofの返り値 |
|---|---|
| “text”, “” | “string” |
| 42, NaN, Infinity | “number” |
| 10n | “bigint” |
| true, false | “boolean” |
| undefined, 未宣言の変数 | “undefined” |
| Symbol() | “symbol” |
| 関数, class, アロー関数 | “function” |
| {}, [], null, new Date() | “object” |
注意点は2つ。NaNは「非数」という名前でもtypeof NaNは"number"です。数値かどうかを厳密に見たいときはNumber.isNaN()やNumber.isFinite()を併用します。もう1つ、classで定義したものも実体は関数なので"function"を返します。
typeof nullが”object”になる歴史的仕様
もっとも有名な落とし穴がtypeof nullです。nullは「値が無い」ことを表すプリミティブですが、typeofは"object"を返します。
typeof null; // "object"("null" ではない)
これはJavaScript最初期の実装に由来するバグで、後方互換のため修正されず仕様として残っています。nullを判定したいときはtypeofに頼らず、value === nullと直接比較するのが確実です。「nullでもundefinedでもない」を見たいときはvalue != null(緩い等価)で両方をまとめて弾けます。
配列・日付はすべて”object” ─ Array.isArrayで判定する
配列・日付・正規表現・プレーンなオブジェクトは、typeofではすべて"object"にまとめられ区別できません。配列かどうかは専用のArray.isArray()を使います。
typeof [1, 2, 3]; // "object"(配列と分からない)
Array.isArray([1, 2, 3]); // true ← 配列判定はこちら
Array.isArray("abc"); // false
配列以外の内部型まで細かく見分けたいときはObject.prototype.toString.call()が使えます。"[object Array]""[object Date]""[object RegExp]"のように内部クラス名を返すため、typeofよりも解像度が高い判定ができます。
typeofでJavaScriptの変数の型を判定する
実務でtypeofを使う場面の中心が、変数や引数の型チェックです。文字列・数値・真偽値・undefinedはtypeofだけで安全に判定できます。
function formatValue(value) {
if (typeof value === "string") return value.trim();
if (typeof value === "number") return value.toFixed(2);
if (typeof value === "boolean") return value ? "ON" : "OFF";
return String(value);
}
未宣言の変数を安全に存在チェックする
typeofには他の演算子にない特性があります。宣言されていない変数に対してもReferenceErrorを投げず、"undefined"を返すという点です。
// someGlobal を直接参照すると未宣言なら例外だが…
if (typeof someGlobal !== "undefined") {
// ここは変数が存在するときだけ安全に通る
someGlobal.init();
}
このため、ライブラリやグローバル変数が読み込まれているかを確かめる「存在チェック」にtypeofが使われます。if (someGlobal)だと未宣言時にエラーになりますが、typeof経由なら落ちません。
typeofで見分けられないケースと使い分け
typeofで足りるのはプリミティブの大分類までで、オブジェクトの中身の型までは判定できません。用途で道具を切り替えてください。
| 判定したいもの | 使う手段 |
|---|---|
| 文字列・数値・真偽値・undefined | typeof |
| 配列かどうか | Array.isArray() |
| null かどうか | value === null |
| 特定クラスのインスタンス | instanceof |
| Date/RegExp などの内部型 | Object.prototype.toString.call() |
typeof new Date(); // "object"(Dateと分からない)
new Date() instanceof Date; // true ← クラス判定は instanceof
とくに外部APIのレスポンスのように「形が保証されない実行時のデータ」をtypeofで一つずつ確かめるのは避けるべきです。プロパティごとにtypeofを並べる検証は抜け漏れやすく、ネストした構造には対応しきれません。この用途にはスキーマ検証ライブラリが向いており、TypeScriptの型と実行時チェックを同時に得られます。導入手順はZodの使い方入門|TypeScript型安全バリデーションにまとめています。
TypeScriptのtypeof型(値から型を取り出す)
ここからは同じ綴りでも意味が変わります。TypeScriptでは、型を書く位置(型注釈やtypeエイリアスの右辺)にtypeofを置くと、その値の型を取り出して再利用できます。実行時には一切動かず、コンパイル時に型として展開されます。
const config = {
host: "localhost",
port: 8080,
};
// 値 config から型を取り出す(型位置の typeof)
type Config = typeof config;
// type Config = { host: string; port: number }
すでにある値を「正」として型を導出できるため、値と型を二重に手書きして食い違わせる事故を防げます。設定オブジェクトや定数テーブルの型を、定義そのものから同期させたいときに使います。
JavaScriptのtypeofとTypeScriptのtypeofの違い
混乱の元はこの2つが同じキーワードなのに正反対だという点です。区別のコツは「typeofがどこに書かれているか」です。式の中(値の位置)にあれば実行時のJavaScript、型注釈の中(値を期待していない位置)にあればTypeScriptの型演算子です。
| 観点 | JavaScriptのtypeof | TypeScriptのtypeof |
|---|---|---|
| 受け取るもの | 値 | 値(を型に変換) |
| 返すもの | 型名の文字列 | 型 |
| いつ動くか | 実行時 | コンパイル時のみ |
| 書く場所 | 式の中 | 型注釈・typeの右辺 |
const x = 10;
const a = typeof x; // 値 "number"(実行時のJS)
type B = typeof x; // 型 number(コンパイル時のTS)
as constと組み合わせてリテラル型を得る
TypeScriptのtypeofはas constと組み合わせると、幅の広いstringではなく具体的な文字列リテラル型が得られます。as constで値をリテラルとして固定し、その型をtypeofで取り出す流れです。
const theme = {
primary: "#3366ff",
radius: 8,
} as const;
type Theme = typeof theme;
// { readonly primary: "#3366ff"; readonly radius: 8 }
as constを付けない場合、primaryはstring、radiusはnumberまで広がってしまいます。定数の値そのものを型として使いたいときはas constとtypeofをセットで使います。
keyof演算子でオブジェクト型のキーをユニオン型にする
keyofはTypeScript専用の型演算子で、オブジェクト型を受け取り、そのプロパティ名を文字列リテラルのユニオン型にして返します。「この型が持つキーのどれか」を表す型を作れます。
type User = { id: number; name: string; email: string };
type UserKey = keyof User;
// "id" | "name" | "email"
keyof typeofイディオム(値からキーの型を得る)
keyofは型を受け取るため、手元にあるのが「型」ではなく「値(オブジェクト)」のときは、先にtypeofで型へ変換してからkeyofを適用します。このkeyof typeofの2段重ねが、実務でもっとも使うイディオムです。
const statusLabels = {
active: "有効",
inactive: "無効",
pending: "保留",
};
// 値 → typeof で型 → keyof でキー集合
type StatusKey = keyof typeof statusLabels;
// "active" | "inactive" | "pending"
読み解く順番は内側からです。typeof statusLabelsでオブジェクトの型を取り出し、その型にkeyofを掛けてキーのユニオン型を得ています。定数テーブルのキーを型として使い回したいときの定番で、キーを増やせば型も自動で追随します。
型安全なプロパティアクセス関数を書く
keyofの典型的な使いどころが、ジェネリクスと組み合わせた型安全なプロパティ取得です。キーの型をK extends keyof Tで縛ることで、存在しないキーを渡すとコンパイルエラーになります。
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "Sato" };
getProperty(user, "name"); // string 型で返る
getProperty(user, "age"); // エラー: "age" は keyof user にない
返り値の型T[K](インデックスアクセス型)により、nameを渡せばstring、idを渡せばnumberと、キーに応じた正確な型が返ります。文字列を直接受け取る素朴な実装ではこの安全性は得られません。
よくある質問
typeof nullが”object”になるのはなぜですか?
JavaScript最初期の実装に由来する歴史的なバグで、後方互換性のために修正されず仕様として残っているためです。nullを判定するときはtypeofを使わず、value === nullと直接比較してください。
配列かどうかをtypeofで判定できますか?
できません。typeof [1,2,3]は"object"を返し、通常のオブジェクトと区別できません。配列判定にはArray.isArray()を使ってください。より細かい内部型はObject.prototype.toString.call()で見分けられます。
JavaScriptのtypeofとTypeScriptのtypeofは同じものですか?
綴りは同じですが別物です。式の中で使うtypeofは実行時のJavaScriptで、値の型名を文字列で返します。型注釈やtypeの右辺で使うtypeofはTypeScriptの型演算子で、値から型を取り出します。書かれている場所で見分けてください。
keyof typeofはどんなときに使いますか?
実行時のオブジェクト(定数テーブルや設定オブジェクト)のキー集合を、型として使いたいときに使います。typeofで値を型に変換し、keyofでそのキーのユニオン型を得ます。キーを追加すれば型も自動で更新されます。
C#やJavaのtypeofとは違うのですか?
別物です。この記事で扱うのはJavaScript/TypeScriptのtypeofで、JavaScriptでは型名の文字列を返し、TypeScriptでは型を取り出します。C#やJavaのtypeof(型情報・クラスオブジェクトの取得)とは動作も用途も異なります。言語を取り違えないよう注意してください。