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).