Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Примітиви та об'єкти в пам'яті». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Примітиви лежать у стеку і копіюються за значенням, а об'єкти лежать у купі, тоді як у стеку зберігається лише посилання на них, тому копіюється саме посилання.** Через це дві змінні з примітивами повністю незалежні, а дві змінні з одним об'єктом бачать одні й ті самі зміни. Spread (`{ ...obj }`, `[...arr]`) дає нове посилання лише на верхньому рівні, вкладені об'єкти залишаються спільними, тому для повністю незалежної копії потрібен `structuredClone()`. ```javascript let a = 10; let b = a; // копія значення b = 20; console.log(a); // 10 const user1 = { name: 'Tim' }; const user2 = user1; // копія посилання user2.name = 'Alex'; console.log(user1.name); // "Alex" ``` **Ключове:** стек зберігає значення і посилання, купа зберігає самі об'єкти, тож копіювання змінної з об'єктом копіює вказівник, а не дані.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**JavaScript тримає дані у двох місцях пам'яті: у стеку (stack) лежать примітиви, а в купі (heap) лежать об'єкти, і в стеку на них зберігається лише посилання.** Саме тому примітив копіюється за значенням, а об'єкт, за посиланням. ## Теорія ### TL;DR - Стек, це швидка пам'ять для примітивів і для посилань на об'єкти. - Купа, це гнучка пам'ять, де лежать самі об'єкти, масиви, функції, `Date`, `Map`, `Set`. - Примітив копіюється за значенням: у нової змінної власна копія даних. - Об'єкт копіюється за посиланням: обидві змінні дивляться на одну ділянку купи. - Spread розриває посилання лише на верхньому рівні; для вкладених структур потрібна глибока копія (`structuredClone()`). ### Швидкий приклад ```javascript // примітив: копія значення let a = 10; let b = a; b = 20; console.log(a, b); // 10 20 // об'єкт: копія посилання const user1 = { name: 'Tim' }; const user2 = user1; user2.name = 'Alex'; console.log(user1.name, user2.name); // "Alex" "Alex" ``` ### Примітиви: стек і копіювання за значенням Примітиви, це `string`, `number`, `boolean`, `null`, `undefined`, `symbol`, `bigint`. Вони зберігаються **у стеку**, швидкій ділянці пам'яті, де лежать самі значення, а не посилання. ```javascript let a = 10; let b = a; // копіюється значення 10 b = 20; console.log(a); // 10 console.log(b); // 20 ``` У пам'яті (stack): ```text +---------------+ | a = 10 | | b = 20 | <- окрема копія +---------------+ ``` Кожна змінна зберігає **власне значення**, тому зміни в одній не впливають на іншу. ### Об'єкти: купа і копіювання за посиланням Об'єкти, а разом з ними масиви, функції, дати та інші складні структури, зберігаються **в купі**, області пам'яті для «гнучких» структур. А **в стеку** лежить лише **посилання (вказівник)** на цей об'єкт. ```javascript const user1 = { name: 'Tim' }; const user2 = user1; // копіюється посилання (вказівник) user2.name = 'Alex'; console.log(user1.name); // "Alex" ``` У пам'яті: ```text STACK (стек) HEAP (купа) +---------------+ +----------------------+ | user1 --+ | | { name: "Alex" } | | user2 --+---------> +----------------------+ +---------------+ ``` Обидві змінні вказують на один і той самий об'єкт, тому зміна через `user2` видно і в `user1`. ### Поверхнева копія і чому вона не рятує вкладені об'єкти Спеціальні способи копіювання створюють **нове посилання**: ```javascript const arr1 = [1, 2, 3]; const arr2 = [...arr1]; // оператор spread arr2.push(4); console.log(arr1); // [1, 2, 3] console.log(arr2); // [1, 2, 3, 4] ``` У пам'яті: ```text arr1 --> [1, 2, 3] arr2 --> [1, 2, 3, 4] ``` Різні посилання, тож зміни не зачіпають оригінал. Але якщо об'єкт вкладений, spread робить лише **поверхневу копію** (shallow copy): ```javascript const user1 = { name: 'Tim', address: { city: 'Kyiv' } }; const user2 = { ...user1 }; user2.address.city = 'London'; console.log(user1.address.city); // "London" ``` Чому так: ```text user1.address --+ user2.address --+--> вказують на один і той самий внутрішній об'єкт ``` Тобто внутрішні об'єкти **все ще копіюються за посиланням**. ### Глибоке копіювання (deep copy) Щоб отримати **повністю незалежну копію**, треба «розірвати» всі посилання всередині. Старий спосіб: ```javascript const user1 = { name: 'Tim', address: { city: 'Kyiv' } }; const user2 = JSON.parse(JSON.stringify(user1)); user2.address.city = 'Berlin'; console.log(user1.address.city); // "Kyiv" ``` Сучасний спосіб (Node.js 17+ та браузери приблизно з 2022 року): ```javascript const user2 = structuredClone(user1); ``` `structuredClone()` робить **глибоку копію** і не лишає спільних посилань узагалі. ### Stack проти heap і підсумкова таблиця ```text STACK (швидка пам'ять) ------------------------ a = 5 b = "Hello" user -> (посилання на об'єкт) HEAP (гнучка пам'ять) ------------------------ { name: "Tim", age: 25 } ``` Стек зберігає прості значення і посилання, а купа, самі складні об'єкти. Побутова аналогія: - **Примітив**, це аркуш паперу з числом: зробив копію, отримав ще один аркуш, і зміна числа на ньому не псує оригінал. - **Об'єкт**, це тека з документами: якщо ти даєш колезі посилання на *ту саму* теку, ви обидва бачите одні й ті самі файли. | Тип даних | Де зберігається | Як копіюється | Що передається | | --- | --- | --- | --- | | Примітиви (`string`, `number`, `boolean`, `null`, `undefined`, `symbol`, `bigint`) | Stack | За значенням | Саме значення | | Об'єкти (`object`, `array`, `function`, `Date`, `Map`, `Set`) | Heap | За посиланням | Вказівник на об'єкт | Коротко: - Примітиви копіюються **за значенням**, створюється нова копія. - Об'єкти копіюються **за посиланням**, обидві змінні «дивляться» на одну ділянку пам'яті. - Для незалежної копії об'єкта використовуй `structuredClone()`, `JSON.parse(JSON.stringify(obj))` або `lodash.cloneDeep()`. ### Типові помилки - Думати, що `[...arr]` чи `{ ...obj }` дають повну копію. Вони копіюють лише верхній рівень. - Вважати, що `const` робить об'єкт незмінним. `const` фіксує посилання, а не вміст купи. - Застосовувати `JSON.parse(JSON.stringify(obj))` до чого завгодно: він втрачає `undefined`, функції та `Symbol`, перетворює `Date` на рядок і падає на циклічних посиланнях. - Забувати, що рядки в JavaScript незмінні: `str[0] = 'X'` нічого не змінить, бо примітив не редагується на місці. - Очікувати, що великий об'єкт «звільниться» одразу після присвоєння `null` одній зі змінних. Поки на нього є хоча б одне живе посилання, garbage collector його не збере.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.