Middle: питання на співбесіді з теми «Типи й приведення»
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
4 питання
Усі числа в JavaScript (крім BigInt) - 64-бітні числа з плаваючою комою за стандартом IEEE 754. Більшість десяткових дробів, як 0.1, у двійковій системі - нескінченні дроби, тож зберігаються з крихітною похибкою.
0.1 + 0.2; // 0.30000000000000004
0.1 + 0.2 === 0.3; // false
1.005 * 100; // 100.49999999999999
Це не баг JavaScript - так само поводяться float у PHP, Python, Java.
Як порівнювати дроби: з допуском:
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON; // true
Як рахувати гроші:
- У найменших одиницях - копійках, як цілі числа:
1999замість19.99. Цілі числа доNumber.MAX_SAFE_INTEGER(≈ 9 квадрильйонів) представлені точно. - Округлення - явне й в одному місці:
Math.round(price * quantity). - Для складних розрахунків (відсотки, курси, розподіл) - бібліотеки десяткової арифметики (
decimal.js,big.js,dinero.js). - Показ користувачу - через
Intl.NumberFormat:
new Intl.NumberFormat('uk-UA', { style: 'currency', currency: 'UAH' }).format(1999 / 100);
// '19,99 ₴'
toFixed() - для показу, не для розрахунків: він повертає рядок і сам округлює з тією ж похибкою ((1.005).toFixed(2) дає '1.00').
У логічному контексті (if, &&, ||, !, тернарний оператор) кожне значення перетворюється на true чи false. Хибних значень небагато:
false, 0, -0, 0n, '' (порожній рядок), null, undefined, NaN.
Усе інше - істинне, і саме тут сюрпризи:
Boolean('0'); // true - непорожній рядок
Boolean('false'); // true
Boolean([]); // true - порожній масив
Boolean({}); // true - порожній об'єкт
Де це підводить:
- Нуль як валідне значення:
function setVolume(level) {
if (!level) level = 50; // гучність 0 перетвориться на 50
}
Правильно: level ??= 50 чи явна перевірка level === undefined.
- Перевірка порожнього масиву:
if (items)завжди істинне. Потрібноif (items.length > 0). - Рендер у React:
{count && <Badge />}приcount === 0виведе на сторінку0. Правильно:{count > 0 && <Badge />}. - Рядки з форми: значення з
<input>завжди рядок, і'0'- істинний.
!!value чи Boolean(value) явно перетворюють значення на логічне - корисно, коли функція має повертати саме true/false.
NaN (Not a Number) - спеціальне значення типу number, яке означає результат невизначеної чи неможливої числової операції:
typeof NaN; // 'number'
Number('abc'); // NaN
0 / 0; // NaN
Math.sqrt(-1); // NaN
undefined + 1; // NaN
NaN не дорівнює нічому, навіть собі. Так визначено стандартом IEEE 754 для чисел з рухомою комою:
NaN === NaN; // false
NaN == NaN; // false
const x = NaN;
x !== x; // true - єдине значення з такою властивістю
Тому if (value === NaN) ніколи не спрацює.
Як перевіряти правильно:
Number.isNaN(value); // true лише для справжнього NaN
Object.is(value, NaN); // теж true
Не плутати з глобальною isNaN(). Вона спершу перетворює аргумент на число, тож відповідає на інше питання - «чи стане це NaN після перетворення»:
isNaN('abc'); // true - 'abc' перетворився на NaN
Number.isNaN('abc'); // false - це рядок, а не NaN
isNaN(undefined); // true
isNaN(''); // false - '' перетворюється на 0
Для перевірки «чи є це коректним числом» найзручніше Number.isFinite(value): false для NaN, Infinity і будь-яких нечислових типів.
NaN «заражає» обчислення: будь-яка арифметика з NaN дає NaN. Одне некоректне значення в масиві перетворить суму на NaN:
[10, 20, Number('')].reduce((a, b) => a + b); // 30 ('' дає 0)
[10, 20, Number('x')].reduce((a, b) => a + b); // NaN
Звідси правило: перевіряти дані на вході (при парсингу форми чи відповіді API), а не шукати, де з'явився NaN, у кінці розрахунку.
Різні методи по-різному бачать NaN:
[NaN].includes(NaN)→true(алгоритм SameValueZero);[NaN].indexOf(NaN)→-1(строга рівність);new Set([NaN, NaN]).size→1;JSON.stringify(NaN)→'null'-NaNтихо перетворюється наnullпри відправці на сервер.
Об'єкт Date зберігає одну мить у часі - кількість мілісекунд від 1970-01-01 UTC. Часового поясу в ньому немає: методи на кшталт getHours() показують цю мить у поясі браузера користувача, а getUTCHours() - в UTC.
Головні пастки:
1. Місяці з нуля.
new Date(2026, 0, 31); // 31 січня 2026
new Date(2026, 1, 31); // 3 березня - «переповнення» тихо переноситься
2. Розбір рядків залежить від формату.
new Date('2026-10-04'); // північ за UTC (лише дата - як UTC)
new Date('2026-10-04T10:00'); // 10:00 за місцевим часом браузера
new Date('04.10.2026'); // залежить від рушія - не використовувати
Користувач у Києві побачить new Date('2026-10-04').getDate() як 4, а в Нью-Йорку - як 3: північ UTC там ще попередній день.
3. Об'єкт змінюваний.
const start = new Date();
const end = start;
end.setDate(end.getDate() + 7); // змінили і start теж
4. Арифметика місяців. setMonth(month + 1) від 31 січня дає 3 березня (лютого 31-го не існує).
Як працювати безпечно:
- обмінюватися з сервером у ISO 8601 з поясом:
2026-10-04T07:00:00Zчи з зміщенням+03:00. Laravel за замовчуванням серіалізує дати моделей саме так (UTC зZ); - зберігати й передавати мить у UTC, а в місцевий час перетворювати лише для показу;
- показувати через
Intl.DateTimeFormat, а не збирати рядок вручну:
new Intl.DateTimeFormat('uk', {
dateStyle: 'long',
timeStyle: 'short',
timeZone: 'Europe/Kyiv',
}).format(new Date('2026-10-04T07:00:00Z'));
// '4 жовтня 2026 р. о 10:00'
- «дата без часу» (день народження, дата події) - не перетворювати на
Dateвзагалі, а тримати рядком'2026-10-04', інакше часовий пояс зсуне її на день; - відносний час -
Intl.RelativeTimeFormat('uk')(«3 дні тому»).
Що далі. Новий API Temporal розділяє поняття: Temporal.PlainDate (дата без часу й поясу), Temporal.ZonedDateTime (час у конкретному поясі), Temporal.Instant (мить) - і робить їх незмінними. Він поступово з'являється в браузерах; до повної підтримки - поліфіл або бібліотеки (date-fns, Day.js, Luxon).