Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Типи даних у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Типи даних у JavaScript** поділяються на сім примітивів і тип object. Примітиви (`string`, `number`, `boolean`, `null`, `undefined`, `symbol`, `bigint`) копіюються за значенням. Об'єкти (масиви, функції, дати) копіюються за посиланням. ```javascript typeof 42; // "number" typeof null; // "object" (баг, не перевірка примітиву) typeof []; // "object" typeof 123n; // "bigint" ``` **Ключове:** `null` є примітивом, але `typeof null === "object"`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Типи даних у JavaScript** поділяються на дві групи: сім **примітивів** (незмінні, передаються за значенням) та **об'єкти** (змінні, передаються за посиланням). ## Теорія ### TL;DR - Примітив, це як надрукована листівка: копіюєш і отримуєш незалежний дублікат. - Об'єкт, це як спільний Google Doc: усі «копії» вказують на одні й ті самі дані. - Сім примітивів: `undefined`, `null`, `boolean`, `number`, `string`, `symbol`, `bigint`. - Об'єкти охоплюють звичайні об'єкти, масиви, функції, дати, регулярні вирази та інше. - `typeof null` повертає `"object"`, а не `"null"`. Відомий баг з першої версії JavaScript. ### Швидкий приклад ```javascript // Примітиви: присвоєння копіює значення let a = 5; let b = a; // b отримує власну копію значення 5 b = 10; console.log(a); // 5, не змінилось // Об'єкти: присвоєння копіює посилання let x = { val: 5 }; let y = x; // y вказує на той самий об'єкт y.val = 10; console.log(x.val); // 10, x теж змінився ``` Ці два приклади пояснюють більшість багів, пов'язаних з цією темою. ### Головна різниця Коли присвоюєш примітив, JavaScript копіює саме значення в окрему ділянку пам'яті. Дві змінні стають незалежними. З об'єктами інакше: змінна тримає посилання (адресу в пам'яті), а не самі дані. Присвоїш таку змінну іншій, і обидві вказуватимуть на один і той самий об'єкт у heap. Зміниш через одну, побачиш результат через обидві. ### Коли що використовувати - Лічильник у циклі → примітив `number`. - Ім'я користувача → примітив `string`. - Профіль користувача з кількома полями → об'єкт. - Прапорець у стані React → примітив `boolean`. - Дані форми в стані React → об'єкт. - Цілі числа, більші за 2^53 - 1 → `bigint`. ### Модель пам'яті V8 зберігає примітиви у стеку (stack) для швидкого доступу. Об'єкти йдуть у heap, а у стеку живе лише вказівник. Саме цей вказівник копіюється під час присвоєння, тому дві змінні можуть непомітно ділити одні й ті самі дані. `Object.create` та схожі API напряму маніпулюють цими структурами в heap. ### Типові помилки **Помилка 1: масив сприймають як примітив** ```javascript let arr1 = [1, 2]; let arr2 = arr1; // копія посилання, не новий масив arr2.push(3); console.log(arr1); // [1, 2, 3], несподівано ``` Масиви є об'єктами. Виправлення: `let arr2 = [...arr1]` або `arr1.slice()`. **Помилка 2: спроба змінити рядок посимвольно** ```javascript let s = "hello"; s[0] = "H"; // помилки немає, але нічого не змінюється console.log(s); // "hello" ``` Рядки незмінні. Щоб замінити символ: `s = "H" + s.slice(1)`. **Помилка 3: порівняння об'єктів через ===** ```javascript console.log({} === {}); // false, різні посилання console.log([1] === [1]); // false ``` Об'єкти порівнюються за посиланням, а не за вмістом. Для структурної перевірки використовуй `JSON.stringify` або `lodash.isEqual`. Про те, як поводиться `==` з різними типами, читай у статті [про перетворення типів у JavaScript](/questions/type-coercion-in-javascript). **Помилка 4: typeof null** ```javascript typeof null === "object" // true typeof null === "null" // false ``` Це баг з JavaScript 1.0 у Netscape. Внутрішній тег типу для `null` збігся з тегом для об'єктів. Пропозиції виправити провалились через зворотну сумісність. Перевіряй `null` явно: `value === null`. **Помилка 5: BigInt у JSON** ```javascript const id = 123n; JSON.stringify(id); // TypeError ``` JSON не підтримує BigInt. Конвертуй перед серіалізацією: `id.toString()`, або передай функцію-замінник у `JSON.stringify`. ### Де зустрічається в реальних проектах - **React**: пропси як об'єкти `{ userId: 123, name: "Alice" }`; ключі списків як примітиви `key={id}`. - **Express**: `req.body` - об'єкт, розпарсений з JSON; `res.status(200)` приймає числовий примітив. - **Node.js fs**: `err` у колбеках - або `null` (примітив), або об'єкт `Error`. - **Redux**: стан - дерево об'єктів; тип дії (action type) - рядковий примітив `"INCREMENT"`. - **Lodash**: `_.cloneDeep(obj)` глибоко копіює вкладені об'єкти без спільних посилань. ### Питання на співбесіді **Q:** Що станеться, якщо передати примітив у функцію і змінити його всередині? **A:** Функція отримає копію. Зміни всередині не зачеплять оригінальну змінну. З об'єктами навпаки: функція отримує посилання і може змінити оригінал. **Q:** Чому `typeof null` повертає `"object"`? **A:** Це баг з першого релізу JavaScript у Netscape. Оригінальний тег типу для null збігся з тегом для об'єктів. Кілька пропозицій виправити провалились, бо зміна зламала б існуючі сайти. **Q:** Назви всі сім примітивів. **A:** `undefined`, `null`, `boolean`, `number`, `string`, `symbol` (ES2015), `bigint` (ES2020). **Q:** Чим `BigInt` відрізняється від `Number`? **A:** `Number` - 64-бітний float з межею безпечних цілих 2^53 - 1. `BigInt` підтримує цілі числа довільного розміру. Вони не перетворюються один в одного неявно, тому `1n + 1` кине TypeError. **Q (для досвідчених):** Об'єкт передано в асинхронний колбек. До моменту виклику колбека об'єкт змінено ззовні. Що побачить колбек? **A:** Змінену версію. Колбек тримає посилання на той самий об'єкт у heap, а не знімок стану. Це поширена причина багів із застарілими даними, коли спільний об'єкт змінюється між плануванням і виконанням колбека. ## Приклади ### Примітиви та об'єкти при передачі у функцію ```javascript function addTen(n) { n = n + 10; // змінює лише локальну копію } function addScore(obj) { obj.score = 100; // змінює сам об'єкт } let num = 5; addTen(num); console.log(num); // 5, не змінилось let user = { name: "Alice" }; addScore(user); console.log(user.score); // 100, змінилось ``` Передав примітив, і будь-яка мутація залишається всередині функції. Передав об'єкт, і виклик побачить кожну зміну. Я бачив, як це спантеличує junior-розробників, які думали, що JavaScript завжди робить копію аргументу. ### Витягування примітиву з пропса в React ```javascript function UserProfile({ user }) { // Витягуємо рядковий примітив з об'єкта-пропса в локальний стан const [name, setName] = useState(user.name); // Редагування поля змінює лише локальний стан // user.name залишається незачепленим return <input value={name} onChange={e => setName(e.target.value)} />; } ``` `useState(user.name)` копіює рядкове значення, а не посилання на `user`. Локальні зміни ніколи не мутують пропс. Якби замість `user.name` у `useState` передавався сам `user`, будь-яка мутація цього об'єкта ділилась би з батьківським компонентом.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.