Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Примус типів у JavaScript (неявний vs явний)». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Примус типів (type coercion)** - це коли JavaScript перетворює значення з одного типу в інший, автоматично або за твоєю командою. Неявний примус відбувається під час операцій (`"5" + 3` дає `"53"`). Явний - це коли сам викликаєш `Number()`, `String()` або `Boolean()`. ```javascript "5" + 3; // "53" - рядок перемагає у + "5" - 3; // 2 - рядок стає числом Number("5"); // 5 - явна конвертація ``` **Головне правило:** використовуй `===` замість `==`, щоб порівняння не запускало примус типів.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Примус типів (type coercion)** - це перетворення значення з одного типу в інший, автоматичне під час операції або явне, коли ти сам викликаєш функцію конвертації. ## Теорія ### TL;DR - Неявний примус відбувається сам. `"5" + 3` дає `"53"`, а не `8`. - Явний - це коли ти сам пишеш `Number()`, `String()` або `Boolean()`. - Оператор `+` конкатенує, якщо хоча б один операнд є рядком. Всі інші математичні оператори перетворюють на число. - `==` запускає примус перед порівнянням. `===` - ні. - Рівно вісім значень є хибними (falsy). Решта правдива, включаючи `[]` і `{}`. ### Швидкий приклад ```javascript // Неявний: JavaScript вирішує сам "5" + 3; // "53" - рядок перемагає у + "5" - 3; // 2 - рядок стає числом true + 1; // 2 - true стає 1 null + 5; // 5 - null стає 0 // Явний: вирішуєш ти Number("5"); // 5 String(42); // "42" Boolean(0); // false ``` У `"5" + 3` JavaScript бачить рядок і перетворює число. У `"5" - 3` оператор віднімання не має рядкового сенсу, тому рядок стає числом. ### Три контексти неявного примусу Неявне перетворення відбувається у трьох ситуаціях. **Рядковий контекст** (оператор `+` з рядком): інше значення стає рядком. `"5" + null` дає `"5null"`. `"5" + {}` дає `"5[object Object]"`. Все, що торкається `+` поряд з рядком, перетворюється на рядок. **Числовий контекст** (`-`, `*`, `/`, `%`, унарний `-`): операнди стають числами. `"6" * "2"` дає `12`. `null` стає `0`. `undefined` стає `NaN`. `true` стає `1`, `false` стає `0`. **Булевий контекст** (умовні вирази, `!`, `||`, `&&`): значення перевіряються як хибні або правдиві. Порожній рядок хибний. Нуль хибний. `null`, `undefined` і `NaN` хибні. Але `[]` і `{}` правдиві. Саме це регулярно дивує людей. ### Вісім хибних значень | Значення | Тип | | --- | --- | | `false` | Boolean | | `0` | Number | | `-0` | Number | | `0n` | BigInt | | `""` | String | | `null` | null | | `undefined` | undefined | | `NaN` | Number | Все решта правдиве. `"0"`, `"false"`, `[]`, `{}` - правдиві. Це класичне питання на співбесіді і класичне джерело багів у продакшені. ### Інструменти явного примусу `Number(value)` перетворює за правилами специфікації. `Number("")` дає `0`, `Number(null)` дає `0`, `Number(undefined)` дає `NaN`, `Number("abc")` дає `NaN`. `parseInt("42px")` зупиняється на першому нечисловому символі й повертає `42`. `parseFloat("3.14abc")` робить те саме для десяткових чисел. Обидва корисні при обробці введення користувача або CSS-значень. `String(value)` - безпечний спосіб перетворити що завгодно на рядок. `String(null)` дає `"null"`, тоді як `null.toString()` кидає TypeError. `Boolean(value)` і подвоєне заперечення `!!` дають однаковий результат. `!!` просто коротше у виразах. ### Нестроге vs суворе порівняння `==` застосовує правила примусу перед порівнянням. `===` порівнює тип і значення разом, без жодного перетворення. [Строгий оператор рівності (`===`)](/questions/equality-operators-in-javascript) - стандартний вибір для більшості порівнянь. ```javascript "5" == 5; // true - рядок стає числом null == undefined; // true - спеціальне правило специфікації null == 0; // false - null дорівнює лише null або undefined "" == false; // true - обидва стають 0 [] == false; // true - [] стає "", "" стає 0 ``` Правило `null == undefined` закладено навмисно. Це єдина пара значень, яка рівна одна одній через `==` і нічому іншому. Запис `if (value == null)` ловить обидва варіанти однією перевіркою, і деякі команди використовують це свідомо. ### Типові помилки Ці приклади з'являються на співбесідах. Буквально одні й ті самі кейси. ```javascript [] + []; // "" - обидва стають "", конкатенація [] + {}; // "[object Object]" - [] це "", {} стає рядком {} + []; // 0 - {} тут парситься як блок, +[] це унарний плюс від [] true + true; // 2 - обидва стають 1 "2" + "2" - "2"; // 20 - "2"+"2" це "22", потім "22"-"2" це 20 NaN === NaN; // false - NaN не рівний сам собі typeof NaN; // "number" - так, саме так ``` Результат `{} + []` залежить від контексту. На початку рядка в консолі `{}` сприймається як блок операторів, тому `+[]` - це просто унарний плюс від порожнього масиву, що дає `0`. Всередині виразу на кшталт `var x = {} + []` отримаєш `"[object Object]"`. Я одного разу витратив годину на дебаг компонента, який відображав `0` замість нічого. Причиною виявився `count && <Component />`, де `count` дорівнював нулю. [NaN](/questions/nan-in-javascript) провокує схожі сюрпризи, коли некоректні числові конвертації течуть через обчислення без перевірки. ### Де це зустрічається в реальному коді - React-умови: `count && <Component />` рендерить текст `0`, коли `count` дорівнює нулю. Краще `count > 0 && <Component />`. - URL-параметри: `Number(searchParams.get("page"))` повертає `0`, якщо параметр відсутній. Перевіряй на `null` до конвертації. - Відповіді API: бекенд може надіслати `"true"` як рядок. `Boolean("true")` дає `true`, і `Boolean("false")` теж. Для рядкових булевих порівнюй через `=== "true"`. - Поля форм: `input.value` завжди рядок. Множення перетворює рядки на числа само, але `+` конкатенує. Викликай `Number()` явно. ### Питання на співбесіді **Q:** Чому `typeof NaN` повертає `"number"`? **A:** NaN визначений у IEEE 754 як числовий тип. Це результат некоректних обчислень: `0/0` або `parseInt("abc")`. JavaScript слідує цій специфікації. **Q:** Яка різниця між `null == undefined` і `null === undefined`? **A:** `null == undefined` дає `true` за спеціальним правилом специфікації. `null === undefined` дає `false`, бо типи різні. Це один з небагатьох випадків, де `==` поводиться передбачувано. **Q:** Коли взагалі варто використовувати `==` замість `===`? **A:** Основний випадок - `if (value == null)`, що ловить і `null`, і `undefined` разом. Поза цим патерном - `===` скрізь. **Q:** Як JavaScript перетворює об'єкт у примітив під час примусу? **A:** Спочатку викликає `valueOf()`, потім `toString()`. Якщо жоден не повертає примітив, виникає TypeError. Масиви отримують `toString()` через `join("")`, тому `[] + []` дає `""`. **Q:** Що поверне `[] == ![]`, і чому? **A:** `true`. `![]` дає `false`, бо `[]` правдивий і заперечується. Далі `[] == false`: `[]` стає `""`, `""` стає `0`, `false` стає `0`. Отримуємо `0 == 0`, що `true`. Саме такі приклади пояснюють, чому досвідчені розробники уникають `==`. ## Приклади ### Явна конвертація при обробці форм ```javascript function calculateTotal(priceInput, quantityInput) { const price = Number(priceInput.value); const quantity = Number(quantityInput.value); if (isNaN(price) || isNaN(quantity)) { return "Некоректні дані"; } return price * quantity; } // priceInput.value = "10", quantityInput.value = "3" // "10" * "3" = 30 - неявний примус спрацює // "10" + "3" = "103" - але + конкатенує замість додавання ``` `input.value` завжди рядок. Множення перетворює рядки на числа само по собі, тому неявний шлях дає правильне число. Але один випадковий `+` замість `*` - і замість суми отримуєш конкатенацію. Явний `Number()` показує намір і ловить `NaN` до того, як він потрапить у підсумкову суму. ### Пастка `+` в React ```javascript function CartSummary({ items }) { const count = items.length; return ( <div> {/* Баг: рендерить "0" як текст, коли count дорівнює 0 */} {count && <p>Товарів у кошику: {count}</p>} {/* Виправлення: спочатку перетворити на булеве */} {count > 0 && <p>Товарів у кошику: {count}</p>} {/* Або тернарний оператор */} {count ? <p>Товарів у кошику: {count}</p> : null} </div> ); } ``` Коли `count` дорівнює `0`, вираз `0 && <p>...</p>` скорочується до `0`, і React рендерить число `0` у DOM. Цей баг регулярно спливає на code review. `count > 0` дає булеве значення до виконання `&&`, тому React отримує `false` і нічого не рендерить. ### Примус при роботі з відповідями API ```javascript async function getUser(id) { const response = await fetch(`/api/users/${id}`); const data = await response.json(); // API повертає: { active: "true", score: "42", role: null } return { id: data.id, active: data.active === "true", // явно, не Boolean("true") score: Number(data.score) || 0, // 0 як fallback якщо NaN role: data.role ?? "guest", // ??, не ||, щоб дозволити 0 і "" }; } ``` `Boolean("false")` дає `true`, бо будь-який непорожній рядок правдивий. Для рядкових булевих з API порівнюй через `=== "true"` напряму. Оператор `??` повертає fallback тільки для `null` і `undefined`, не для `0` чи `""`, тому він безпечніший за `||` для числових значень за замовчуванням.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.