Що таке nan у JavaScript?
NaN - це спеціальне числове значення в JavaScript, яке сигналізує про недійсний результат математичної операції, наприклад ділення нуля на нуль або парсинг нечислового рядка.
Теорія
TL;DR
- NaN означає "Not-a-Number," але його тип все одно
"number"(typeof NaN === "number") - Уяви отруєну пігулку: один NaN у розрахунку заражає всі наступні результати
NaN !== NaNзавждиfalse. Не баг, це специфікаціяNumber.isNaN()- правильний спосіб перевірки. ГлобальнийisNaN()спочатку перетворює рядки і дає хибні результати
Швидкий приклад
console.log(0 / 0); // NaN
console.log(typeof NaN); // "number"
console.log(NaN === NaN); // false
console.log(Number.isNaN(NaN)); // true
const result = "abc" * 5;
console.log(result + 10); // NaN (розповсюджується)NaN заражає. Будь-яка арифметика з NaN повертає NaN. Один поганий вхід може зруйнувати цілий ланцюг обчислень без жодної помилки.
Чому NaN !== NaN
Стандарт IEEE 754 визначає NaN як "невпорядковане" значення. Два недійсних результати можуть походити від абсолютно різних операцій: 0/0 і Math.sqrt(-1) - обидва NaN, але це різні типи недійсності. Тому стандарт каже: рівність з NaN завжди повертає false. Це змушує явно виявляти його, а не випадково довіряти.
V8 зберігає NaN як бітовий патерн 0x7FF8000000000000. Оператор === порівнює бітові патерни, і будь-яке порівняння з NaN за специфікацією повертає false. Без винятків.
Коли використовувати Number.isNaN()
- Парсинг введення: перевіряй
Number.isNaN()перед будь-яким розрахунком з даними від користувача - API відповіді: заміни NaN на
nullперед відправкою JSON (JSON.stringifyвсе одно конвертує NaN уnull, що може непомітно зламати математику на клієнті) - Агрегація масивів: відфільтруй NaN перед викликом
.reduce() - Де б ти не використовував глобальний
isNaN(): заміни його наNumber.isNaN()
Як JavaScript створює NaN
Недійсні числові операції породжують NaN: ділення нуля на нуль, квадратний корінь з від'ємного числа, парсинг нечислового рядка через Number() або parseInt(). Після цього будь-яка арифметика з NaN автоматично повертає NaN. Зараження відбувається само по собі.
Поширені помилки
Порівняння через ===
if (result === NaN) { /* ніколи не виконається */ }NaN не дорівнює нічому, навіть собі. Рішення: Number.isNaN(result).
Використання глобального isNaN() на рядках
isNaN("hello"); // true (перетворено в NaN)
isNaN("123abc"); // true (перетворено в NaN)
isNaN("123"); // false (перетворено в 123)Глобальний isNaN() спочатку конвертує аргумент. Він не може відрізнити "це насправді NaN" від "цей рядок просто не парситься як число." Використовуй Number.isNaN(Number(value)), коли потрібні обидві перевірки.
NaN в JSON
JSON.stringify({ score: NaN }); // {"score":null}У JSON немає NaN. Серіалізація конвертує його у null. Якщо клієнт прочитає цей null і спробує робити математику, результати будуть неправильними без жодної помилки. Очищуй дані перед stringify.
NaN отруює Math.min / Math.max
Math.min(1, NaN, 3); // NaNОдин NaN отруює весь агрегат. Спочатку відфільтруй масив.
Де зустрічається в реальних проєктах
- React / Formik:
Number.isNaN()у валідаторах числових полів перед відправкою форми - Express: парсинг
req.query.ageчерезparseInt(), перевірка NaN перед запитами до бази - D3.js: фільтрація NaN з масивів даних графіків перед обчисленням шкал
- Lodash:
_.isNaN()як зручна обгортка в утилітах
Питання на співбесіді
Q: Чому typeof NaN === "number", якщо NaN не є дійсним числом?
A: Система типів JavaScript слідує IEEE 754. NaN - це частина простору чисел з плаваючою точкою, просто недійсний стан усередині нього. Позначка типу технічно правильна.
Q: Яка різниця між isNaN() і Number.isNaN()?
A: Глобальний isNaN() перетворює аргумент, тому isNaN("foo") повертає true. Number.isNaN() пропускає перетворення і повертає true тільки для самого NaN, тому Number.isNaN("foo") повертає false.
Q: Як обробити NaN в array reduce?
A: Фільтруй перед reduce: .filter(v => !Number.isNaN(v)). Або захисти callback з fallback значенням.
Q: Що робить JSON.stringify з NaN?
A: Конвертує NaN у null. У JSON немає представлення NaN. Завжди очищуй дані перед серіалізацією.
Q: (Senior) V8 зберігає NaN як 0x7FF8000000000000. Чому +NaN === NaN все одно повертає false?
A: Унарний + повертає той самий бітовий патерн. Але IEEE 754 вказує, що будь-яке порівняння з NaN повинно повертати false незалежно від цього. Це навмисно, щоб недійсні значення не могли випадково пройти перевірки рівності.
Приклади
Парсинг введення користувача
const userAge = "25a";
const age = parseInt(userAge, 10); // NaN
if (Number.isNaN(age)) {
console.log("Невалідний вік"); // виконується
} else {
console.log(`Вік через 5 років: ${age + 5}`);
}
// Output: "Невалідний вік"parseInt зупиняється на першому нечисловому символі і повертає NaN. Перевіряй до розрахунків, а не після.
Виправлення NaN в array reduce
const prices = ["$10", "20", "abc"];
// Без виправлення: NaN отруює суму
const broken = prices.reduce((sum, p) => sum + Number(p), 0);
console.log(broken); // NaN
// З виправленням: спочатку фільтруй недійсні значення
const total = prices
.map(p => Number(p.replace("$", "")))
.filter(v => !Number.isNaN(v))
.reduce((sum, v) => sum + v, 0);
console.log(total); // 30Саме такий патерн ламав продакшен дашборди, де одне погане значення з API перетворювало цілий блок статистики на NaN. Виправлення займає один рядок.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.