Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Різниця між примітивами та непримітивами в JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Примітиви** - незмінні значення (`string`, `number`, `boolean`, `null`, `undefined`, `bigint`, `symbol`), що копіюються за значенням. **Непримітиви** (об'єкти, масиви, функції) - змінювані, копіюються за посиланням (reference). ```javascript let a = 5; let b = a; a = 10; console.log(b); // 5 — власна копія let x = {}; let y = x; console.log(x === y); // true — одне посилання ``` **Ключове:** присвоєння копіює значення для примітивів, але лише pointer для об'єктів.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Примітиви** - це незмінні значення, які зберігаються безпосередньо в пам'яті за значенням; **непримітиви** (об'єкти) - це змінювані структури, до яких звертаються через посилання (reference) на heap-пам'ять. ## Теорія ### TL;DR - Примітив - як паперова листівка: копія повністю незалежна, зміна копії не торкається оригіналу - Об'єкт - як посилання на спільний Google Doc: всі хто тримають це посилання редагують один документ - Присвоєння примітива копіює значення; присвоєння об'єкта копіює посилання (pointer) - Прості стабільні дані (вік, ID, прапорець) -> примітив; згруповані або змінювані дані (кошик, конфіг, профіль) -> об'єкт - `===` для примітивів порівнює значення; `===` для об'єктів перевіряє чи це одне й те саме посилання в пам'яті ### Швидкий приклад ```javascript // Примітиви: присвоєння копіює значення let a = 5; let b = a; // b отримує власну копію числа 5 a = 10; console.log(a); // 10 console.log(b); // 5 — не змінилось // Об'єкти: присвоєння копіює посилання let x = { count: 5 }; let y = x; // y вказує на той самий об'єкт що і x x.count = 10; console.log(x.count); // 10 console.log(y.count); // 10 — той самий об'єкт, той самий результат ``` `x` і `y` вказують на один об'єкт у пам'яті. Другого об'єкта немає. Тому мутація через `x` видна через `y`. ### Головна різниця Коли ти пишеш `let b = a` з примітивом, JavaScript створює новий слот у пам'яті і копіює туди саме значення. Коли ти пишеш `let y = x` з об'єктом, JavaScript копіює лише адресу об'єкта (64-бітний pointer у V8). Обидві змінні вказують на одне місце в heap-пам'яті. Будь-яка мутація через одну змінну відображається в іншій. ### Коли використовувати - Вік, ціна, прапорець (toggle), ID користувача -> примітив - Кошик, профіль користувача, конфіг застосунку -> об'єкт - Унікальний ключ, який не повинен випадково збігатись з чимось -> `Symbol` (примітив) - Згруповані дані з методами -> об'єкт ### Таблиця порівняння | Аспект | Примітиви | Непримітиви (Об'єкти) | |---|---|---| | Зберігання | Стек (пряме значення) | Heap (дані) + стек (посилання) | | Присвоєння | Копіює значення | Копіює посилання | | Змінюваність | Незмінні | Змінювані | | Порівняння `===` | Перевіряє значення | Перевіряє посилання | | Розмір | Фіксований, малий | Динамічний | | Типи | `string`, `number`, `boolean`, `null`, `undefined`, `bigint`, `symbol` | `{}`, `[]`, `() => {}` | | Коли використовувати | Константи, ID, обчислення | Структури даних, конфіги, стан | ### Як це працює всередині V8 (Chrome і Node.js) розміщує примітиви на стеку для швидкого доступу без потреби в garbage collection. Об'єкти йдуть на heap; стек тримає лише 64-бітний pointer на це місце. Коли ти пишеш `obj.prop = 1`, V8 іде за pointer-ом і мутує спільні heap-дані. Коли на об'єкт більше ніщо не вказує, garbage collector звільняє пам'ять. Ще один нюанс: V8 інтернує (intern) короткі ідентичні рядки, тобто вони можуть ділити один слот у пам'яті. Це оптимізація, яку ти не контролюєш, але саме тому однакові рядкові примітиви завжди `===` рівні. ### Типові помилки **Помилка 1: очікування що масив копіюється при присвоєнні** ```javascript let arr1 = [1, 2]; let arr2 = arr1; // копіює посилання, не елементи arr2.push(3); console.log(arr1); // [1, 2, 3] — несподівано // Виправлення: використай spread let arr2 = [...arr1]; ``` **Помилка 2: спроба змінити рядок напряму** ```javascript let str = 'hello'; str[0] = 'H'; console.log(str); // 'hello' — рядки незмінні, присвоєння не дає ефекту // Виправлення: будуй новий рядок str = 'H' + str.slice(1); // 'Hello' ``` **Помилка 3: порівняння об'єктів через `===`** ```javascript console.log({ a: 1 } === { a: 1 }); // false — два різних посилання // Виправлення: порівнюй за вмістом JSON.stringify(obj1) === JSON.stringify(obj2); // Для вкладених структур: lodash isEqual ``` **Помилка 4: забути що `null` це примітив** ```javascript let obj = null; obj.prop = 'test'; // TypeError: Cannot set properties of null // Виправлення: перевір перед зверненням if (obj) obj.prop = 'test'; ``` ### Де це зустрічається - React: стан - це об'єкт (`useState({ name: 'Alice' })`); для оновлення створюй новий об'єкт (`{ ...user, name: 'Bob' }`), бо пряма мутація обходить механізм виявлення змін React - Express: `req.body` - змінюваний об'єкт; `res.status(200)` приймає примітивне число - Node.js: `process.env` - об'єкт, спільний для всього застосунку; `parseInt(process.env.PORT)` дає примітив - Lodash `cloneDeep`: коли потрібна повністю незалежна копія вкладеного об'єкта, а не копія pointer-а На практиці модель посилань породжує більше багів ніж модель значень. Більшість питань «чому мій стан змінився несподівано?» в React зводяться до спільних посилань. ### Питання на співбесіді **Q:** Що відбувається після `let x = 5; x = { value: 5 };`? **A:** `x` переходить від зберігання примітивного числа до зберігання посилання на новий об'єкт. JavaScript не накладає обмежень на типи змінних, тому перепризначення просто спрацьовує. Число `5` зникає. **Q:** Чому `typeof null` повертає `'object'`? **A:** Це баг 1995 року, який так і не виправили заради зворотної сумісності. `null` - примітив. Результат `typeof` тут хибний, але зміна зламала б занадто багато існуючого коду. **Q:** Чи можуть примітиви мати методи як `.toUpperCase()` або `.toString()`? **A:** Так, через auto-boxing. Коли ти викликаєш `'hello'.toUpperCase()`, JavaScript тимчасово загортає рядковий примітив у об'єкт `String`, викликає метод, потім відкидає обгортку. Оригінальний примітив залишається незмінним. **Q:** Якщо передати масив у функцію і всередині зробити `push`, чи зміниться оригінальний масив? **A:** Так. Масиви - об'єкти, тому передача масиву передає посилання. Будь-яка мутація всередині функції зачіпає оригінал. Щоб уникнути цього, передавай копію: `fn([...arr])`. **Q:** (Senior) Два об'єкти з однаковими властивостями дають `===` false. Як перевірити структурну рівність? **A:** `JSON.stringify(a) === JSON.stringify(b)` працює для плоских об'єктів з однаковим порядком ключів. Для вкладених структур або об'єктів з `Date` чи `RegExp` краще lodash `isEqual`, який коректно обробляє крайні випадки. ## Приклади ### Примітив: копія значення у функції ```javascript function increment(n) { n += 1; return n; } let count = 5; let result = increment(count); console.log(count); // 5 — оригінал не змінився console.log(result); // 6 ``` Функція отримує копію числа `5`. Зміна `n` всередині нічого не робить з `count` зовні. Це модель передачі за значенням у дії. ### Посилання об'єкта: баг з мутацією стану в React ```javascript // ПОГАНО: мутує існуючий об'єкт стану const [user, setUser] = useState({ name: 'Alice', prefs: { theme: 'dark' } }); user.prefs.theme = 'light'; // пряма мутація setUser(user); // React бачить те саме посилання — перерендеру немає // ДОБРЕ: створюй новий об'єкт на кожному рівні що змінився setUser({ ...user, prefs: { ...user.prefs, theme: 'light' } }); // React бачить нове посилання, перерендер відбувається коректно ``` `setUser(user)` передає той самий pointer. Поверхнева перевірка React бачить однакове посилання і пропускає перерендер. Виправлення завжди одне: повертай новий об'єкт, бо React покладається на нерівність посилань для виявлення змін.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.