Avatar ·

Как правильно проверить, является ли строка числом в JavaScript? Функция IsNumeric()

Всем привет! Пишу на JavaScript и столкнулся с типичной проблемой — нужно проверять, является ли введённое пользователем значение числом. Например, есть форма, где пользователь вводит цену товара, и я хочу убедиться, что это именно число, а не текст. Пробовал использовать isNaN(), но он ведёт себя странно. Например:
console.log(isNaN('123')); // false — вроде число
console.log(isNaN('12.3')); // false — тоже число
console.log(isNaN('')); // false — пустая строка считается числом?!
console.log(isNaN('  ')); // false — пробелы тоже число?
console.log(isNaN('abc')); // true — это понятно
console.log(isNaN(true)); // false — true=1, это число?!
console.log(isNaN(null)); // false — null=0, тоже число?!
Мне нужно, чтобы функция IsNumeric() возвращала true только для нормальных десятичных чисел (целых и дробных с точкой), а для пустых строк, пробелов, булевых значений, null, undefined и прочего мусора — false. Ещё хочется, чтобы поддерживались отрицательные числа и числа с плавающей точкой (например, "-123.45"). А вот научная нотация (1e5) и шестнадцатеричные числа (0xFF) мне не нужны. Подскажите, как написать такую функцию корректно и надёжно? Желательно с учётом разных браузеров и edge-случаев. Спасибо! <<>>

Проблема встроенных методов и правильное решение

Вы совершенно правы: isNaN() и Number() имеют неочевидное поведение. isNaN() сначала преобразует аргумент в число, поэтому '' становится 0, true — 1, null — 0. Это неприемлемо для валидации пользовательского ввода.

Лучший способ — использовать комбинацию typeof, parseFloat() и isFinite(). Вот оптимальная реализация:

function isNumeric(value) {
  if (typeof value !== 'string' && typeof value !== 'number') {
    return false;
  }
  return !isNaN(value) && !isNaN(parseFloat(value)) && isFinite(value);
}

Как это работает

  • typeof value !== 'string' && typeof value !== 'number' — отсекаем булевы значения, null, undefined, объекты. Нам нужны только строки и числа.
  • !isNaN(value) — проверяем, что само значение не является NaN (например, NaN или undefined).
  • !isNaN(parseFloat(value)) — parseFloat() пытается извлечь число из строки. Для '' и ' ' он вернёт NaN (что нам и нужно). Для '123abc' вернёт 123 — это может быть нежелательно, поэтому идём дальше.
  • isFinite(value) — отсекает Infinity, -Infinity и NaN. Также проверяет, что число не слишком большое/маленькое.

Дополнительное улучшение: строгая проверка

Если нужно, чтобы строка содержала только число (без лишних символов вроде '123abc'), добавьте проверку с Number():

function isNumericStrict(value) {
  if (typeof value !== 'string' && typeof value !== 'number') {
    return false;
  }
  const num = Number(value);
  return !isNaN(num) && isFinite(num) && String(num) === value.trim();
}

Здесь String(num) === value.trim() гарантирует, что после преобразования в число и обратно в строку мы получим то же самое. Например:

  • '123' → 123 → '123' — совпадает, true
  • '12.30' → 12.3 → '12.3' — не совпадает, false (если это важно)
  • ' 123 ' → 123 → '123' — не совпадает из-за пробелов

Тесты для обоих вариантов

// Первая функция (либеральная)
console.log(isNumeric('123'));     // true
console.log(isNumeric('-123.45')); // true
console.log(isNumeric('0.5'));    // true
console.log(isNumeric(''));       // false
console.log(isNumeric('  '));     // false
console.log(isNumeric('abc'));    // false
console.log(isNumeric(true));     // false
console.log(isNumeric(null));     // false
console.log(isNumeric(Infinity)); // false

// Вторая функция (строгая)
console.log(isNumericStrict('123'));   // true
console.log(isNumericStrict(' 123 ')); // false (пробелы)
console.log(isNumericStrict('12.30')); // false (лишний ноль)
console.log(isNumericStrict('123abc'));// false

Рекомендация

Для большинства задач (валидация форм, ввод цен) достаточно первой версии. Вторая нужна, если критично точное совпадение с числовым форматом. Обе функции работают во всех современных браузерах и Node.js.

<<>>

Альтернативный подход с регулярным выражением

Если вы уверены, что входные данные — всегда строка, можно использовать регулярное выражение. Оно даёт полный контроль над форматом:

function isNumericRegex(value) {
  if (typeof value !== 'string') return false;
  return /^-?\d+(\.\d+)?$/.test(value.trim());
}

Это выражение проверяет:

  • ^-? — необязательный минус в начале
  • \d+ — одна или более цифр
  • (\.\d+)? — необязательная дробная часть с точкой
  • $ — конец строки

Примеры

console.log(isNumericRegex('123'));     // true
console.log(isNumericRegex('-123.45')); // true
console.log(isNumericRegex('0.5'));    // true
console.log(isNumericRegex('.'));      // false
console.log(isNumericRegex('12.'));    // false
console.log(isNumericRegex(''));       // false
console.log(isNumericRegex(' 123 '));  // false (пробелы)

Плюсы: максимально строгий контроль, легко модифицировать (добавить запятую, пробелы, научную нотацию).
Минусы: не работает с числами (только строки), нужно помнить про экранирование.

Выбирайте подход под свою задачу: для надёжности и гибкости — первый способ с isFinite, для жёсткой валидации формата — регулярное выражение.