Валидация десятичных чисел в JavaScript: почему IsNumeric() ломает код и как это исправить
Каждый разработчик, который писал на JavaScript хотя бы неделю, рано или поздно сталкивался с задачей: «Проверить, является ли строка числом». Казалось бы, что может быть проще? Встроенная функция isNaN() или parseFloat() — и дело в шляпе. Но не тут-то было. Вопрос «Validate decimal numbers in JavaScript — IsNumeric()» на Stack Overflow собрал сотни тысяч просмотров и десятки противоречивых ответов. Почему? Потому что JavaScript — это язык, где typeof NaN === 'number', а пустая строка считается нулём.
В этой статье мы не просто дадим готовую функцию isNumeric(). Мы разберём 5 самых частых и опасных ошибок, которые допускают даже опытные программисты, и покажем, как их избежать. Вы узнаете, почему isNaN — это ловушка, как работает приведение типов и какой метод валидации чисел в JS реально использовать в продакшене.
Почему isNaN() — зло (и когда оно работает)
Давайте начнём с очевидного. Многие новички пишут так:
function isNumeric(n) {
return !isNaN(n);
}
На первый взгляд логично: если не NaN, значит число. Но давайте протестируем:
console.log(isNumeric('123')); // true — ок
console.log(isNumeric('12.3')); // true — ок
console.log(isNumeric('')); // true — УПС! Пустая строка — число?
console.log(isNumeric(' ')); // true — пробел тоже число?
console.log(isNumeric(null)); // true — null — это число?
console.log(isNumeric(true)); // true — true — это 1
Ошибка №1: isNaN() сначала преобразует аргумент в число, а потом проверяет. Пустая строка '' преобразуется в 0, null — в 0, true — в 1. В результате функция считает числами то, что числами не является.
Есть строгий вариант — Number.isNaN() (ES6), но он проверяет только, является ли значение именно NaN, без преобразования. Для валидации чисел он тоже не годится.
parseFloat: герой или злодей?
Второй популярный подход — использовать parseFloat():
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
Этот вариант уже лучше, но у него другая проблема:
console.log(isNumeric('123abc')); // true — parseFloat откусит '123', а 'abc' проигнорирует
console.log(isNumeric('12.3.4')); // true — вторую точку parseFloat просто не заметит
console.log(isNumeric('Infinity')); // true — бесконечность — это число?
Ошибка №2: parseFloat() парсит строку до первого недопустимого символа. Строка '123abc' для него — это 123. Но пользователь мог случайно нажать лишнюю клавишу, и мы обязаны это отловить.
Ошибка №3: isFinite() отсекает Infinity и -Infinity, но пропускает NaN и undefined. Проблема в том, что isFinite(null) возвращает true, потому что null преобразуется в 0.
Ошибка №4: Регулярные выражения — панацея или новый ад?
Когда разработчики понимают, что встроенные функции ненадёжны, они часто бросаются в другую крайность — регулярные выражения. И тут начинается настоящий хаос.
Вот типичная, но опасная регулярка:
function isNumeric(n) {
return /^\d+$/.test(n);
}
Проблемы:
- Не пропускает десятичные точки (
'12.3'— false) - Не пропускает отрицательные числа (
'-5'— false) - Не пропускает научную нотацию (
'1e5'— false)
Вот «улучшенная» версия, которую часто копируют с форумов:
function isNumeric(n) {
return /^-?\d+(\.\d+)?$/.test(n);
}
Выглядит солидно, но пропускает пустую строку после знака минус ('-' — true). А ещё не учитывает пробелы и не работает с экспоненциальной записью.
Вывод: регулярные выражения — мощный инструмент, но для валидации чисел их нужно писать с учётом всех форматов, которые JavaScript считает числами. А это 0x1A (шестнадцатеричные), 0b101 (двоичные), 1e-3 (экспонента) и даже 0.1e+2.
Ошибка №5: Забываем про пробелы и пустые строки
Самая коварная ошибка — не обрезать строку перед проверкой. Пользователи часто случайно ставят пробел в начале или конце. Даже самая умная функция isNumeric споткнётся о ' 123 ', если не применить trim().
function isNumeric(n) {
if (typeof n !== 'string') return false;
return !isNaN(n) && !isNaN(parseFloat(n));
}
Этот код всё ещё пропустит ' 123 ' как число? Нет, parseFloat обрезает пробелы автоматически. Но Number(' 123 ') тоже вернёт 123. Проблема в другом: если вы не хотите принимать строки с пробелами, нужно явно это проверять.
Идеальная функция IsNumeric: пошаговая сборка
Теперь, когда мы разобрали все подводные камни, давайте напишем правильную функцию валидации десятичных чисел в JavaScript. Она должна:
- Принимать только строки (или числа, если нужно)
- Отсекать пустые строки и строки из пробелов
- Проверять, что строка является конкретным числовым значением (не Infinity, не NaN)
- Поддерживать десятичные дроби, отрицательные числа и научную нотацию
- Не пропускать «мусор» типа
'123abc'
function isNumeric(value) {
// Если значение уже число — проверяем, не NaN и не Infinity
if (typeof value === 'number') {
return isFinite(value);
}
// Если не строка — сразу false
if (typeof value !== 'string') {
return false;
}
// Убираем пробелы