Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Junior: питання на співбесіді з теми «Типи й приведення»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

4 питання

  • === (строга рівність) - true, лише якщо однакові і тип, і значення. Без перетворень.
  • == (нестрога) спершу приводить операнди до спільного типу за досить заплутаними правилами.
1 === '1';        // false
1 == '1';         // true
0 == '';          // true
0 == '0';         // true
'' == '0';        // false - рівність не транзитивна
null == undefined; // true
null == 0;        // false
[] == false;      // true
NaN === NaN;      // false

Правило: завжди === і !==. Більшість лінтерів (правило eqeqeq) так і вимагає.

Єдиний поширений виняток: value == null - коротка перевірка «null або undefined» одночасно. Але й тут читабельніше value === null || value === undefined або оператор ??.

Ще нюанси:

  • NaN не дорівнює нічому, навіть собі. Перевірка - Number.isNaN(x).
  • Об'єкти й масиви порівнюються за посиланням: [1] === [1] - false, бо це два різні масиви.
  • Object.is(a, b) - як ===, але Object.is(NaN, NaN) дає true, а Object.is(0, -0) - false.

Докладніше в документації: Порівняння рівності

  • undefined - «значення немає, бо його не задали»: неініціалізована змінна, відсутня властивість, параметр без аргументу, результат функції без return.
  • null - «значення свідомо немає»: його присвоюють явно.
let a;              // undefined
const user = {};
user.phone;         // undefined
const selected = null; // свідомо «нічого не вибрано»
typeof undefined;   // 'undefined'
typeof null;        // 'object' - історична помилка мови

|| проти ??:

  • a || b повертає b, якщо a будь-яке хибне значення: 0, '', false, NaN, null, undefined.
  • a ?? b повертає b, лише якщо a - null або undefined.
const page = 0;
page || 1;   // 1 - помилка: нуль - валідне значення
page ?? 1;   // 0

const title = '';
title || 'Без назви';  // 'Без назви'
title ?? 'Без назви';  // ''

Правило: для значень за замовчуванням - ??, якщо 0, '' чи false мають бути збережені.

Поруч живе ?. (optional chaining): user?.address?.city повертає undefined, якщо будь-яка ланка null чи undefined, замість TypeError. І присвоєння ??=: options.timeout ??= 5000.

Докладніше в документації: Оператор ??

У JavaScript сім примітивних типів і один об'єктний:

Тип Приклад
string 'Київ'
number 42, 3.14, NaN, Infinity
bigint 9007199254740993n
boolean true, false
undefined змінна без значення
null «навмисно порожньо»
symbol Symbol('id')
object об'єкти, масиви, функції, дати, Map...

Примітиви незмінні й передаються за значенням:

let a = 'текст';
let b = a;
b += '!';
a;   // 'текст' - зміна b не торкнулася a

const s = 'привіт';
s.toUpperCase();   // повертає НОВИЙ рядок, s не змінився

Об'єкти передаються за посиланням: змінна зберігає не сам об'єкт, а посилання на нього.

const user = { name: 'Оля' };
const same = user;
same.name = 'Іра';
user.name;   // 'Іра' - це той самий об'єкт

function rename(obj) { obj.name = 'Петро'; }
rename(user);   // змінює оригінал

Порівняння теж відрізняється: примітиви порівнюються за значенням, об'єкти - за посиланням:

'a' === 'a';           // true
{} === {};             // false - два різні об'єкти
[1, 2] === [1, 2];     // false

Як у примітива є методи? 'текст'.length працює, бо рушій на мить «загортає» примітив у тимчасовий об'єкт-обгортку (String), викликає метод і викидає обгортку. Створювати обгортки вручну (new String('a')) не варто: new String('a') === 'a' - false, а new Boolean(false) у умові - істинний.

Чому це важливо на практиці:

  • зміна об'єкта в одній частині коду видна всюди, де є посилання на нього, - джерело багатьох помилок. Звідси звичка створювати копії ({ ...user }, toSorted()) замість змін на місці;
  • реактивність Vue і стан React покладаються на це: React порівнює стан за посиланням і не помітить зміни всередині того самого об'єкта;
  • typeof повертає 'object' для null і масивів - історична особливість, тому для перевірки масиву є Array.isArray.

Докладніше в документації: Примітив

Значення з полів форми, URL і dataset - завжди рядки. Перетворити їх на число можна кількома способами, і вони поводяться по-різному.

Number(value) і унарний плюс +value - однаково строгі: весь рядок має бути числом.

Number('42');      // 42
Number(' 42 ');    // 42 - пробіли по краях ігноруються
Number('42px');    // NaN
Number('3.14');    // 3.14
Number('');        // 0 - пастка!
Number(null);      // 0
Number(undefined); // NaN
+'42';             // 42

parseInt(value, radix) читає ціле число з початку рядка й зупиняється на першому недопустимому символі:

parseInt('42px');       // 42
parseInt('3.99');       // 3 - дробова частина відкидається
parseInt('px42');       // NaN
parseInt('ff', 16);     // 255

Завжди передавайте основу числення другим аргументом (parseInt(value, 10)) - так намір очевидний, а лінтери цього вимагають.

parseFloat(value) - те саме для дробових: parseFloat('3.14abc') → 3.14.

Що обрати:

  • дані з форми, де очікується саме число - Number(), і перевіряти результат. Він не «пропустить» '12abc';
  • значення з одиницями CSS ('16px') - parseInt/parseFloat;
  • +value - коротко, але менш помітно в коді.

Пастки:

  • порожній рядок дає 0, а не NaN. Порожнє поле «кількість» перетвориться на нуль. Порожнечу треба перевіряти окремо;
  • parseInt з числом, а не рядком: parseInt(0.0000005) → 5, бо число спершу перетворюється на рядок '5e-7'. Для округлення чисел є Math.trunc, Math.round;
  • parseInt як колбек map: ['1', '2', '3'].map(parseInt) дає [1, NaN, NaN], бо map передає індекс другим аргументом, і він стає основою числення. Правильно: .map(Number) чи .map((s) => parseInt(s, 10));
  • кома як десятковий роздільник: українські користувачі пишуть '12,5', а Number('12,5') - це NaN. Для введення сум потрібна нормалізація (value.replace(',', '.')) або поле type="number", значення якого браузер віддає з крапкою.

Перевірка результату: Number.isNaN(n) чи Number.isFinite(n) (відкидає і NaN, і Infinity).

Докладніше в документації: parseInt()