Глибоке копіювання
Глибока копія об'єкта – це копія, чиї властивості не поділяють посилань (що вказують на ті самі значення) з властивостями об'єкта-джерела, з якого була зроблена копія. Як наслідок, коли змінити або джерело, або копію, то можна мати певність, що інший об'єкт змінюватися не буде. Така логіка відрізняється від логіки поверхневого копіювання, в якій зміни вкладених властивостей у джерелі або копії можуть призвести також до змін іншого об'єкта.
Два об'єкти o1
і o2
є структурно еквівалентними, якщо їхня видима поведінка – однакова. Ця поведінка включає наступне:
- Властивості
o1
іo2
мають одні й ті ж значення в тому самому порядку. - Значення їхніх властивостей є структурно еквівалентними.
- Їхні ланцюжки прототипів структурно еквівалентні (хоча, коли розглядається структурна еквівалентність, то об'єкти зазвичай є простими об'єктами, тобто вони обидва успадковують від
Object.prototype
).
Структурно еквівалентні об'єкти можуть бути або одним і тим же об'єктом (o1 === o2
), або копіями (o1 !== o2
). Оскільки еквівалентні примітивні значення завжди однакові при порівнянні, то їх не можна скопіювати.
Глибокі копії можна більш формально визначити так:
- Вони не є одним об'єктом (
o1 !== o2
). - Властивості
o1
іo2
мають однакові назви в тому самому порядку. - Значення їхніх властивостей є глибокими копіями одне одних.
- Їхні ланцюжки прототипів структурно еквівалентні.
Глибокі копії можуть мати або не мати скопійовані ланцюжки прототипів (і нерідко не мають). Але два об'єкти зі структурно нееквівалентними ланцюжками прототипів (наприклад, один є масивом, а інший – простим об'єктом) ніколи не є копіями одне одного.
Копія об'єкта, всі властивості якого мають примітивні значення, вписується у визначення як глибокої копії, так і поверхневої копії. Проте дещо безглуздо говорити про глибину такої копії, адже вона не має вкладених властивостей, а зазвичай про глибоке копіювання говорять у контексті змін вкладених властивостей.
У JavaScript стандартні вбудовані операції копіювання об'єктів (синтаксис розгортання, Array.prototype.concat()
, Array.prototype.slice()
, Array.from()
і Object.assign()
) не створюють глибоких копій (замість цього вони створюють поверхневі копії).
Один зі способів створити глибоку копію об'єкта JavaScript, якщо він може бути серіалізований, – скористатися JSON.stringify()
для перетворення об'єкта на рядок JSON, а потім JSON.parse()
для перетворення рядка назад на (геть новий) об'єкт JavaScript:
const ingredientsList = ["локшина", { list: ["яйця", "борошно", "вода"] }];
const ingredientsListDeepCopy = JSON.parse(JSON.stringify(ingredientsList));
Оскільки глибока копія не поділяє посилань зі своїм об'єктом-джерелом, то всі зміни, що вносяться до глибокої копії, не впливають на об'єкт-джерело.
// Змінити значення властивості 'list' в ingredientsListDeepCopy.
ingredientsListDeepCopy[1].list = ["рисове борошно", "вода"];
// Властивість 'list' не змінюється в ingredients_list.
console.log(ingredientsList[1].list);
// Array(3) [ "яйця", "борошно", "вода" ]
Проте попри те, що об'єкт у коді вище достатньо простий, щоб бути серіалізованим, чимало об'єктів JavaScript не можна серіалізувати взагалі – наприклад, функції (із їхніми замиканнями), символи, об'єкти, що представляють елементи HTML в API DOM HTML, рекурсивні дані та багато інших ситуацій. Виклик JSON.stringify()
для серіалізації об'єктів у таких випадках призведе до помилки. Тому немає способу створити глибокі копії таких об'єктів.
API Вебу structuredClone()
також створює глибокі копії, маючи при цьому перевагу в тому, що дозволяє переносним об'єктам у джерелі переноситися до нової копії, а не просто клонуватися. Він також обробляє більше типів даних, наприклад, Error
. Але слід зауважити, що structuredClone()
не є можливістю мови JavaScript – це можливість браузерів та інших середовищ виконання JavaScript, які мають реалізацію API Вебу. І виклик structuredClone()
для клонування несеріалізовного об'єкта призведе до помилки так само, як і виклик JSON.stringify()
для серіалізації.
Дивіться також
- Споріднені терміни глосарія:
window.structuredClone()