「この変数の中身は文字列なのか、それとも配列なのか」を確かめたい場面は、JavaScriptを書いているとよく出てきます。関数に渡された引数の種類によって処理を分けたいときや、外部から受け取ったデータの形をチェックしたいときなどです。JavaScriptにはデータ型を判定する方法がいくつかあり、代表的なのが typeof・instanceof・Array.isArray の3つです。ただしそれぞれ得意分野と落とし穴があり、使いどころを間違えると誤判定します。この記事では初心者〜中級者の方に向けて、3つの違いと正しい使い分け、より厳密な判定方法までを、動くコードで確認しながら解説します。
目次
判定方法によって得意分野が違う
先に全体像をつかんでおきましょう。3つの判定方法は、それぞれ見ているものが異なります。typeof は値の「大まかな種類」を文字列で返す演算子で、文字列・数値・真偽値といったプリミティブ(基本的な値)の判定に向いています。instanceof は「あるコンストラクタから作られたインスタンスかどうか」を真偽値で返す演算子で、クラスのインスタンスや Date などの組み込みオブジェクトの判定に使います。そして Array.isArray は「配列かどうか」だけを確実に判定する専用のメソッドです。
まずは代表的な値に対して、3つがどんな結果を返すかを見比べてみましょう。同じ値でも判定方法によって結果が変わる様子が分かります。細かい理由はこのあと順番に説明します。
| 値 | typeof | instanceof Object | Array.isArray |
|---|---|---|---|
"hello" | “string” | false | false |
42 | “number” | false | false |
true | “boolean” | false | false |
undefined | “undefined” | false | false |
null | “object” | false | false |
[1, 2, 3] | “object” | true | true |
{ a: 1 } | “object” | true | false |
new Date() | “object” | true | false |
function(){} | “function” | true | false |
typeof の列を見ると、配列・オブジェクト・Date・そして null までもが同じ "object" になっている点に気づきます。この「ざっくりしすぎている」ところが typeof の弱点であり、後半で扱うつまずきポイントの中心です。それでは各方法を順に見ていきましょう。
typeof でプリミティブを判定する
typeof は値の種類を表す文字列を返す演算子です。typeof 値 と書くだけで使え、返ってくる文字列は次の8種類のいずれかです。文字列・数値・真偽値といったプリミティブを判定するときに最も手軽で、こうした用途にはこれが第一候補になります。
| 返る文字列 | 対象となる値 |
|---|---|
"string" | 文字列 |
"number" | 数値(NaN や Infinity も含む) |
"boolean" | true / false |
"undefined" | undefined、未定義のプロパティ |
"bigint" | BigInt(10n のような値) |
"symbol" | Symbol |
"function" | 関数(クラスも含む) |
"object" | 上記以外のオブジェクト、および null |
console.log(typeof "hello"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof 10n); // "bigint"
console.log(typeof Symbol()); // "symbol"
console.log(typeof function () {}); // "function"
// 数値であることの判定はこう書ける
const value = 100;
if (typeof value === "number") {
console.log("これは数値です");
}
戻り値は必ず文字列なので、typeof value === "number" のように文字列と比較して判定します。ここでよくある書き間違いが、比較対象を number(クォートなし)と書いてしまうことです。それでは変数扱いになってしまうため、必ず "number" のように文字列で書きます。
未宣言の変数でもエラーにならない
typeof には他の演算子にない安全な特徴があります。まだ宣言されていない変数に対して使っても、エラーにならず "undefined" を返すのです。通常、宣言されていない変数を参照すると ReferenceError が発生しますが、typeof だけは例外的にこれを起こしません。そのため「そもそもこの変数が存在するかどうか」を安全に確かめる用途に使えます。
// notDeclared はどこにも宣言されていない変数
// console.log(notDeclared); // ReferenceError で止まる
console.log(typeof notDeclared); // "undefined" エラーにならない
// この性質を使って安全に存在チェックできる
if (typeof notDeclared === "undefined") {
console.log("この変数は定義されていません");
}
typeof null が “object” になる落とし穴
typeof で最も有名な落とし穴が、typeof null が "object" を返すことです。これはJavaScriptの初期からある仕様上の不具合として知られており、後方互換性のために今も修正されずに残っています。つまり typeof だけでは null とオブジェクトを区別できません。null かどうかを確かめたいときは、素直に === null で比較します。
console.log(typeof null); // "object" ← 本来は "null" が自然だが仕様上こうなる
// null 判定は typeof ではなく === で行う
const value = null;
if (value === null) {
console.log("これは null です");
}
// 「オブジェクトかつ null ではない」を確かめる定番の書き方
if (typeof value === "object" && value !== null) {
console.log("これは null 以外のオブジェクトです");
}
あわせて覚えておきたいのが null と undefined の区別です。どちらも「値がない」ことを表しますが、typeof undefined は "undefined"、typeof null は "object" と結果が分かれます。両方をまとめて「値が入っていない」と判定したいなら、value == null(等価演算子 == をひとつだけ使う)と書くと null と undefined の両方で true になります。
配列も関数も “object” 側に寄る
もう一つ押さえたいのは、配列も typeof では "object" になることです。冒頭の表のとおり、配列・プレーンなオブジェクト・Date などはすべて "object" でひとくくりにされ、typeof では見分けられません。例外は関数で、関数だけは "function" という専用の結果を返します。したがって「配列かどうか」を typeof で判定することはできず、次に紹介する専用の方法が必要になります。
Array.isArray で配列を判定する
配列かどうかを調べたいときの正解が Array.isArray です。引数に渡した値が配列であれば true、それ以外なら false を返します。typeof では配列とオブジェクトの区別がつかない問題を、このメソッド一つで解決できます。
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray([])); // true 空配列も配列
console.log(Array.isArray({ a: 1 })); // false オブジェクトは配列ではない
console.log(Array.isArray("abc")); // false 文字列も配列ではない
console.log(Array.isArray(null)); // false
// 引数が配列のときだけ処理したい場合
function sum(list) {
if (!Array.isArray(list)) {
throw new Error("配列を渡してください");
}
return list.reduce((a, b) => a + b, 0);
}
instanceof Array より堅牢な理由
配列は次に説明する instanceof を使って value instanceof Array と判定することもできます。多くの場合これでも動きますが、Array.isArray の方が堅牢です。理由は、instanceof が実行環境(レルムと呼ばれる領域)をまたぐと正しく判定できないことにあります。たとえばページ内の iframe の中で作られた配列を、外側のページで受け取ると、両者は別々の Array コンストラクタを持つため instanceof Array が false になってしまいます。Array.isArray はこうした環境の違いに影響されず、どこで作られた配列でも正しく true を返します。
「配列かどうか」を判定したい場面では、迷わず Array.isArray を使うのが安全です。
instanceof でインスタンスを判定する
instanceof は「その値が、指定したコンストラクタから作られたインスタンスか」を判定する演算子です。値 instanceof コンストラクタ の形で書き、真偽値を返します。仕組みとしては、値のプロトタイプチェーン(オブジェクトが継承のためにたどっていく連鎖)をさかのぼり、そこに指定したコンストラクタの prototype が含まれているかを調べています。そのため、自分で定義したクラスのインスタンスや、Date・RegExp といった組み込みオブジェクトの判定に向いています。
class Animal {}
class Dog extends Animal {}
const pochi = new Dog();
console.log(pochi instanceof Dog); // true
console.log(pochi instanceof Animal); // true 継承元でも true になる
console.log(pochi instanceof Object); // true すべてのオブジェクトの元をたどる
// 組み込みオブジェクトの判定にも使える
console.log(new Date() instanceof Date); // true
console.log(/abc/ instanceof RegExp); // true
console.log([1, 2] instanceof Array); // true
継承関係のある Dog のインスタンスが、親クラスの Animal に対しても true を返している点に注目してください。instanceof はプロトタイプチェーンをたどるため、継承元のクラスでも true になります。この性質は「ある系統に属するか」を確かめたいときに便利です。
プリミティブには使えない
注意したいのは、instanceof がオブジェクト(インスタンス)専用だということです。文字列リテラルや数値リテラルのようなプリミティブに対しては、たとえ String や Number と比較しても false を返します。プリミティブとして書いた値は、コンストラクタから new で作ったインスタンスではないためです。
console.log("hello" instanceof String); // false 文字列リテラルはインスタンスではない
console.log(42 instanceof Number); // false
// new で明示的に作ると true になるが、通常こう書くことは推奨されない
console.log(new String("hello") instanceof String); // true
このため、文字列・数値・真偽値といったプリミティブの判定には instanceof ではなく typeof を使います。instanceof はあくまで「インスタンスかどうか」を見る道具だと覚えておきましょう。先に触れたとおり、実行環境(iframe など)をまたぐと判定に失敗することがある点も、頭の片隅に置いておくとよいでしょう。
Object.prototype.toString.call で厳密に見分ける
「null なのか配列なのか Date なのか、まとめて正確に見分けたい」というときに使えるのが Object.prototype.toString.call(値) です。これは値ごとに "[object 型名]" という形式の文字列を返します。typeof では区別できなかった null・配列・Date なども、それぞれ違う結果になるため、汎用的な型判定に向いています。
const toType = (v) => Object.prototype.toString.call(v);
console.log(toType("hello")); // "[object String]"
console.log(toType(42)); // "[object Number]"
console.log(toType(true)); // "[object Boolean]"
console.log(toType(undefined)); // "[object Undefined]"
console.log(toType(null)); // "[object Null]" ← null もちゃんと区別できる
console.log(toType([1, 2, 3])); // "[object Array]" ← 配列も区別できる
console.log(toType({ a: 1 })); // "[object Object]"
console.log(toType(new Date()));// "[object Date]" ← Date も区別できる
console.log(toType(() => {})); // "[object Function]"
Object.prototype.toString をそのまま呼ぶのではなく、call(値) を使って判定したい値に適用しているのがポイントです。この一行を使い回せば、あらゆる値の種類をひとつの方法で見分けられます。型名の部分だけを取り出したいときは、正規表現や slice で "Array" のような文字列を抜き出す関数にしておくと便利です。
NaN の判定は Number.isNaN で行う
型判定に関連してつまずきやすいのが NaN(Not-a-Number、数値でない値)です。typeof NaN は "number" を返すため、型としては数値の仲間です。ところが NaN には「自分自身とも等しくない」という特殊な性質があり、value === NaN のような比較では判定できません。そこで専用の Number.isNaN を使います。
console.log(typeof NaN); // "number" 型としては数値
console.log(NaN === NaN); // false 自分自身とも等しくない
console.log(Number.isNaN(NaN)); // true これが正しい NaN 判定
console.log(Number.isNaN(123)); // false
console.log(Number.isNaN("abc")); // false 文字列は変換せず、NaN そのものではない
// 数値への変換に失敗したかどうかの確認に使える
const n = Number("12px"); // NaN
if (Number.isNaN(n)) {
console.log("数値に変換できませんでした");
}
グローバルの isNaN という関数もありますが、こちらは引数を数値に変換してから判定するため、isNaN("abc") が true になるなど直感に反する結果を返します。「値が本当に NaN かどうか」を確かめたいときは、変換を行わない Number.isNaN を使うのが安全です。
結局どれを使えばいいのか
ここまでの内容をふまえると、判定したい対象ごとに選ぶべき方法が整理できます。文字列・数値・真偽値・undefined といったプリミティブは typeof、配列は Array.isArray、自作クラスや Date などの組み込みインスタンスは instanceof、そして null と非nullオブジェクトの区別や、あらゆる種類を細かく見分けたい厳密な判定は Object.prototype.toString.call という具合です。次の表に用途と選ぶべき方法をまとめておきます。
| 判定したいもの | 使う方法 |
|---|---|
| 文字列・数値・真偽値などのプリミティブ | typeof |
| 未宣言の変数の存在チェック | typeof(エラーにならない) |
| 配列かどうか | Array.isArray |
クラスのインスタンス・Date などの組み込み | instanceof |
null かどうか | === null |
| あらゆる種類を厳密に見分ける | Object.prototype.toString.call |
NaN かどうか | Number.isNaN |
まとめ
JavaScriptのデータ型判定は、対象に合った方法を選ぶことが大切です。typeof はプリミティブの判定に手軽で、未宣言の変数でもエラーにならない一方、null や配列がまとめて "object" になる点に注意します。配列の判定は環境をまたいでも確実な Array.isArray が正解です。instanceof はプロトタイプチェーンをたどってインスタンスかどうかを見るため、クラスや組み込みオブジェクトの判定に向いていますが、プリミティブや iframe をまたぐケースでは使えません。そして null・配列・Date まで正確に見分けたいときは Object.prototype.toString.call が万能です。それぞれの得意分野と落とし穴を押さえれば、型判定で迷うことはなくなります。