フロントエンドの現場で日々コードを書いていると、フォームの入力値チェックやAPIから飛んできた数値データのバリデーションで、数値まわりのバグに頭を抱える瞬間ってありますよね。
「あれ、この値ちゃんと数字のはずなのに、なんで弾かれるんだ?」
「`isNaN`でチェックしたのに、文字列を入れたら`true`になったぞ…?」
JavaScriptの型まわりの挙動、特に「数値ではない何か」を表す `NaN`(Not-a-Number)の判定は、言語の歴史的背景も相まってなかなかにクセモノです。中級からもう一歩上のシニアへステップアップしようとしている君なら、この不可解な挙動の裏側にある「JavaScriptの闇と仕様」を正確に理解しておく必要があります。
今回は、長年の悪夢だったグローバルな `isNaN` と、ES6で救世主として登場した `Number.isNaN` の決定的な違いについて、ブラウザの裏側の処理まで踏み込みながら徹底的に解説していこう。
—
そもそも `NaN` とは何か?(JavaScript最大の皮肉)
まず大前提として知っておいてほしいのは、JavaScriptにおける `NaN` のシュールな仕様です。
console.log(typeof NaN); // “number”
おいおい、ふざけてるのか?と思うかもしれないけれど、これ、仕様(ECMAScript)なんです。`NaN` は「数値を表すはずが失敗したエラー値」という扱いなので、データ型としては数値(Number型)に分類されます。ここがすべての混乱のスタート地点です。
さらに、JavaScriptの `NaN` は、自分自身を含めていかなる値とも等しくならないという孤高の存在です。
console.log(NaN === NaN); // 嘘だろ…?結果は false だ
そのため、普通の比較演算子(`===`)で「この値は NaN かどうか」を判定することはできません。だからこそ、専用の判定関数が必要になるわけです。
—
悪夢の元凶:グローバルな `isNaN()` の正体
さて、本題です。JavaScriptを書き始めた頃によく使うグローバル関数の `isNaN()`。これ、実務では使うな危険とされています。なぜか?
グローバルな `isNaN()` の最大の罪は、「渡された値を、勝手にNumber型に暗黙の型変換(Coercion)してから判定する」というお節介すぎる仕様にあります。
裏側で何が行われているかと言うと、`isNaN(value)` が実行された瞬間、JavaScriptエンジンは内部で `Number(value)` を呼び出します。つまり、数値以外のものが来たら、無理やり数値に変換しようとするんです。
この「お節介」が、現場でどんなバグを生むか見てみましょう。
// 数値以外を渡したときのグローバル isNaN の挙動
console.log(isNaN(NaN)); // true (そりゃそうだ)
console.log(isNaN(“hello”)); // true えっ!?
console.log(isNaN(undefined)); // true マジか…
console.log(isNaN({})); // true 嘘だろ…
console.log(isNaN(“123”)); // false (これは数値になるからセーフ)
見てください、`”hello”`(文字列)や `undefined`、さらには空のオブジェクト `{}` まで `true`(=NaNである)と判定されてしまいました。
“Not-a-Number(数値ではない)” という言葉の定義を広義に解釈しすぎた結果、「数値に変換できないもの=すべてNaN判定」というカオスを生み出してしまったのです。
APIレスポンスでたまたま `undefined` が返ってきただけで「おっ、これNaNだな!」と誤判定され、フォームバリデーションが盛大にバグる……というのは、現場で本当によくある悲劇です。
—
救世主:ES6の `Number.isNaN()`
この状況に耐えかねた仕様策定者たちが、ES6(ECMAScript 2015)でようやくまともな関数を用意してくれました。それが `Number.isNaN()` です。
`Number.isNaN()` は、グローバルなそれとは違い、一切の暗黙の型変換を行いません。
仕様としてのアルゴリズムは非常にシンプルで、以下の2ステップだけです。
1. 引数が Number型であるか をチェックする。数値を表すプリミティブでなければ即座に `false` を返す。
2. その上で、値が `NaN` であるか を厳密にチェックする。
これだけです。余計な気を利かせて勝手に `Number()` でパースしたりはしません。
// Number.isNaN() の厳密な挙動
console.log(Number.isNaN(NaN)); // true (正しくNaNと判定)
console.log(Number.isNaN(“hello”)); // false 「文字列はそもそも数値型じゃないのでNaNではない」
console.log(Number.isNaN(undefined)); // false 「undefinedは数値型じゃないのでNaNではない」
console.log(Number.isNaN({}); // false 「オブジェクトは数値型じゃないのでNaNではない」
console.log(Number.isNaN(123)); // false (当然、普通の数値はfalse)
完璧ですね。私たちが求めていたのはまさにこの挙動です。
「値が本当に `NaN` という特殊な数値そのものであるか」をピンポイントで正確にブチ抜いてくれます。
—
現場で使える実践的Tips:バリデーションの実装パターン
実務のフロントエンド開発において、ユーザーの入力値(`` などから取得した文字列)を数値として安全に扱いたい場面は数え切れません。
ここで、現場でそのままコピペして使える、堅牢なバリデーションのパターンを共有しておきます。
/
- ユーザー入力値が安全な有効な数値であるかを判定する実用的な関数
- @param {unknown} value – 検査する値
- @returns {boolean}
/
function isValidNumber(value) {
// 1. 空文字やboolean、nullなどはあらかじめ弾く(必要に応じて調整)
if (value === “” || value === null || typeof value === “boolean”) {
return false;
}
// 2. パースを試みる
const parsed = Number(value);
// 3. Number.isNaN を使って厳密にチェックする
// (※ Number() は ” 123 ” のような前後の空白を自動除去してくれるので実用上便利)
return !Number.isNaN(parsed);
}
// — テストケース —
console.log(isValidNumber(“123.45″)); // true (数値として扱える文字列)
console.log(isValidNumber(123)); // true (通常の数値)
console.log(isValidNumber(” 42 “)); // true (前後の空白はNumber()がよしなに解釈してくれる)
console.log(isValidNumber(“hello”)); // false (文字列はアウト)
console.log(isValidNumber(undefined));// false (undefinedはアウト)
console.log(isValidNumber(NaN)); // false (本物のNaNも当然アウト)
プロからのアドバイス
実務では、単に `Number.isNaN()` をそのまま呼ぶだけでなく、「そもそもその値がどんな文脈で渡ってくるか」を意識することが重要です。
- 完全に厳密に「値そのものが NaN か」を調べたい場合:迷わず `Number.isNaN(value)` を使う。
- フォームの入力値など、文字列として渡ってきたものを数値に変換してチェックしたい場合:一度 `Number()` や `parseFloat()` で明示的に数値に変換した上で、結果に対して `Number.isNaN()` を適用する。
この2つを頭の中で明確に切り分けられるようになると、型起因のバグを踏む確率が劇的に減ります。
—
まとめ
- グローバルな `isNaN()` は使うな:勝手に暗黙の型変換(`Number(value)`)を行うため、意図しない誤判定(`”hello”` や `undefined` が `true` になるなど)を引き起こす魔物。
- ES6の `Number.isNaN()` を使え:型変換を一切行わず、引数が「Number型かつNaNである」場合のみ `true` を返す、極めて安全で正しいモダンな判定手法。
フロントエンドのコードベースから古いグローバルな `isNaN` を駆逐し、安全で堅牢な型判定を実装していきましょう。こういう細かなベストプラクティスの積み重ねが、バグの少ないクリーンなプロダクトを作ります。チームにもぜひ共有してあげてください!

コメント