Пошук уроків, статей та іншого контенту
Працюватимете з вкладеними об’єктами, посиланнями та відмінностями між примітивним і посилальним копіюванням.
Об’єкт може містити інші об’єкти як значення властивостей. Такі об’єкти називають вкладеними.
const user = {
name: "Олена",
age: 25,
address: {
city: "Львів",
street: "Городоцька"
}
};
console.log(user.name); // Олена
console.log(user.address.city); // ЛьвівЩоб отримати значення з вкладеного об’єкта, використовують кілька крапок:
console.log(user.address.street); // ГородоцькаВластивість address містить інший об’єкт, а цей об’єкт має властивості city і street.
До властивостей вкладеного об’єкта можна звертатися для читання та зміни:
const user = {
name: "Олена",
address: {
city: "Львів"
}
};
user.address.city = "Київ";
console.log(user.address.city); // КиївТакож можна додавати нові властивості:
user.address.postalCode = "01001";
console.log(user.address);
// { city: "Київ", postalCode: "01001" }У JavaScript значення умовно поділяють на дві групи:
примітивні значення: рядки, числа, логічні значення, null, undefined, Symbol і BigInt;
посилальні значення: об’єкти, масиви та функції.
Примітивні значення зберігаються як самі значення. Об’єкти натомість створюються в пам’яті, а змінна містить посилання на них.
Коли змінній присвоюють примітивне значення, копіюється саме значення:
let firstNumber = 10;
let secondNumber = firstNumber;
secondNumber = 20;
console.log(firstNumber); // 10
console.log(secondNumber); // 20Зміна secondNumber не вплинула на firstNumber. У кожної змінної власне значення.
Те саме відбувається з рядками:
let firstName = "Олена";
let secondName = firstName;
secondName = "Андрій";
console.log(firstName); // Олена
console.log(secondName); // АндрійКоли об’єкт присвоюють іншій змінній, копіюється не весь об’єкт, а посилання на нього:
const originalUser = {
name: "Олена",
age: 25
};
const anotherUser = originalUser;
anotherUser.name = "Андрій";
console.log(originalUser.name); // Андрій
console.log(anotherUser.name); // АндрійОбидві змінні посилаються на один і той самий об’єкт. Тому зміна через одну змінну видима через іншу.
Схематично це можна уявити так:
originalUser ──┐
├──> { name: "Андрій", age: 25 }
anotherUser ──┘const не робить об’єкт незміннимКлючове слово const забороняє призначити змінній інше значення, але не забороняє змінювати властивості об’єкта:
const user = {
name: "Олена"
};
user.name = "Андрій"; // Дозволено
console.log(user.name); // АндрійАле повністю замінити об’єкт не можна:
const user = {
name: "Олена"
};
// user = { name: "Андрій" }; // ПомилкаОператори === і !== порівнюють об’єкти за посиланням, а не за вмістом:
const firstUser = {
name: "Олена"
};
const secondUser = {
name: "Олена"
};
console.log(firstUser === secondUser); // falseОб’єкти мають однаковий вміст, але це два різні об’єкти в пам’яті.
Якщо дві змінні посилаються на один об’єкт, порівняння поверне true:
const firstUser = {
name: "Олена"
};
const secondUser = firstUser;
console.log(firstUser === secondUser); // trueЩоб створити окрему копію об’єкта, часто використовують оператор розпилення ...:
const originalUser = {
name: "Олена",
age: 25
};
const copiedUser = {
...originalUser
};
copiedUser.name = "Андрій";
console.log(originalUser.name); // Олена
console.log(copiedUser.name); // Андрій
console.log(originalUser === copiedUser); // falseТепер originalUser і copiedUser — різні об’єкти.
Також для поверхневого копіювання можна використати Object.assign():
const originalUser = {
name: "Олена",
age: 25
};
const copiedUser = Object.assign({}, originalUser);
copiedUser.age = 30;
console.log(originalUser.age); // 25
console.log(copiedUser.age); // 30Поверхнева копія копіює лише властивості першого рівня. Якщо серед властивостей є вкладений об’єкт, його посилання не копіюється окремо:
const originalUser = {
name: "Олена",
address: {
city: "Львів"
}
};
const copiedUser = {
...originalUser
};
copiedUser.name = "Андрій";
copiedUser.address.city = "Київ";
console.log(originalUser.name); // Олена
console.log(originalUser.address.city); // КиївВластивість name була скопійована окремо, тому її зміна не вплинула на оригінал.
Але address залишився спільним вкладеним об’єктом. Зміна copiedUser.address.city змінила також originalUser.address.city.
Схематично:
originalUser ──> { name: "Олена", address ──┐
├──> { city: "Київ" }
copiedUser ──> { name: "Андрій", address ──┘Якщо потрібно змінити вкладений об’єкт без зміни оригіналу, потрібно скопіювати кожен рівень, який змінюється:
const originalUser = {
name: "Олена",
address: {
city: "Львів",
street: "Городоцька"
}
};
const copiedUser = {
...originalUser,
address: {
...originalUser.address,
city: "Київ"
}
};
console.log(originalUser.address.city); // Львів
console.log(copiedUser.address.city); // Київ
console.log(originalUser.address === copiedUser.address); // falseУ цьому прикладі:
оператор ...originalUser створює новий об’єкт користувача;
оператор ...originalUser.address створює новий об’єкт адреси;
властивість city перезаписується новим значенням.
Такий підхід особливо корисний, коли потрібно створити оновлену версію даних, не змінюючи початковий об’єкт.
Масиви також є об’єктами, тому для них діють ті самі правила:
const originalUser = {
name: "Олена",
hobbies: ["читання", "музика"]
};
const copiedUser = {
...originalUser
};
copiedUser.hobbies.push("спорт");
console.log(originalUser.hobbies);
// [ "читання", "музика", "спорт" ]Масив hobbies залишився спільним.
Щоб створити незалежну копію масиву, використайте оператор розпилення:
const originalUser = {
name: "Олена",
hobbies: ["читання", "музика"]
};
const copiedUser = {
...originalUser,
hobbies: [...originalUser.hobbies]
};
copiedUser.hobbies.push("спорт");
console.log(originalUser.hobbies); // [ "читання", "музика" ]
console.log(copiedUser.hobbies); // [ "читання", "музика", "спорт" ]Якщо структура містить багато рівнів вкладеності, копіювати кожен рівень вручну може бути незручно. Для даних, які можна безпечно клонувати, використовують structuredClone():
const originalData = {
user: {
name: "Олена",
settings: {
theme: "dark"
}
},
tags: ["javascript", "beginner"]
};
const copiedData = structuredClone(originalData);
copiedData.user.settings.theme = "light";
copiedData.tags.push("objects");
console.log(originalData.user.settings.theme); // dark
console.log(originalData.tags); // [ "javascript", "beginner" ]
console.log(copiedData.user.settings.theme); // lightstructuredClone() створює незалежну копію вкладеної структури. Однак це не універсальний спосіб копіювання будь-яких значень. Наприклад, функції не можна клонувати за допомогою structuredClone().
Перетворення через JSON.stringify() і JSON.parse() іноді використовують для простих даних, але цей підхід має обмеження:
const originalData = {
name: "Олена",
age: 25
};
const copiedData = JSON.parse(JSON.stringify(originalData));
copiedData.name = "Андрій";
console.log(originalData.name); // Олена
console.log(copiedData.name); // АндрійТакий спосіб не підходить для всіх типів даних і може змінювати або втрачати спеціальні значення. Для сучасного JavaScript за можливості краще використовувати structuredClone() або явно копіювати потрібні рівні через ....
Уявімо, що потрібно оновити налаштування користувача, не змінюючи початкові дані:
const user = {
name: "Олена",
settings: {
language: "uk",
notifications: {
email: true,
push: false
}
}
};
const updatedUser = {
...user,
settings: {
...user.settings,
notifications: {
...user.settings.notifications,
push: true
}
}
};
console.log(user.settings.notifications.push); // false
console.log(updatedUser.settings.notifications.push); // trueБуло створено новий об’єкт на кожному рівні, який змінювався:
новий об’єкт updatedUser;
новий об’єкт settings;
новий об’єкт notifications.
Початковий об’єкт user залишився без змін.
const first = { value: 1 };
const second = first;
second.value = 2;
console.log(first.value); // 2second — не копія, а ще одне посилання на той самий об’єкт.
const first = {
details: {
active: true
}
};
const second = { ...first };
second.details.active = false;
console.log(first.details.active); // falseОператор ... не копіює вкладені об’єкти автоматично.
===const first = { value: 1 };
const second = { value: 1 };
console.log(first === second); // falseОператор === перевіряє, чи це один і той самий об’єкт, а не чи однакові його властивості.
const як гарантії незмінностіconst settings = {
theme: "dark"
};
settings.theme = "light"; // Це дозволеноconst захищає посилання змінної, але не властивості об’єкта.
Якщо змінюється властивість третього рівня, потрібно скопіювати всі рівні від кореня до цієї властивості:
const updated = {
...original,
firstLevel: {
...original.firstLevel,
secondLevel: {
...original.firstLevel.secondLevel,
value: "new value"
}
}
};Об’єкт може містити інші об’єкти та масиви.
Під час присвоєння об’єкта копіюється посилання, а не сам об’єкт.
Дві змінні можуть посилатися на один об’єкт.
Примітивні значення копіюються незалежно одне від одного.
Оператор ... створює поверхневу копію.
Вкладені об’єкти та масиви після поверхневого копіювання можуть залишатися спільними.
Для безпечної зміни вкладеної структури потрібно копіювати кожен змінюваний рівень.
structuredClone() допомагає створювати глибоку копію підтримуваних даних.
=== порівнює об’єкти за посиланням, а не за вмістом.
const не забороняє змінювати властивості об’єкта.