Поскольку объекты в #JavaScript являются ссылочными значениями, их нельзя просто скопировать с помощью =. Но не беспокойтесь, существует 3 способа клонирования объекта 👍.
const food = { beef: '🥩', bacon: '🥓' } // "Spread" { ...food } // "Object.assign" Object.assign({}, food) // "JSON" JSON.parse(JSON.stringify(food)) // RESULT: // { beef: '🥩', bacon: '🥓' }
Объекты — это ссылочные типы
Почему нельзя использовать =? Посмотрим, что может произойти:
const obj = {one: 1, two: 2}; const obj2 = obj; console.log( obj, // {one: 1, two: 2}; obj2 // {one: 1, two: 2}; )
Оба объекта выдают одно и то же. На данный момент никаких проблем. Рассмотрим, что произойдет после редактирования второго объекта:
const obj2.three = 3; console.log(obj2); // {one: 1, two: 2, three: 3}; <-- ✅ console.log(obj); // {one: 1, two: 2, three: 3}; <-- 😱
obj2 был изменен, однако изменения коснулись и obj. Причина заключается в том, что объекты являются ссылочными типами. Поэтому при использовании =, указатель копируется в область занимаемой памяти. Ссылочные типы не содержат значений, они являются указателем на значение в памяти.
Использование Spread
С помощью spread можно клонировать объект. Обратите внимание, что копия будет неглубокой. На момент публикации этого руководства оператор spread для клонирования объектов находился на стадии 4, соответственно официально он не указан в спецификациях. Поэтому для того, чтобы его использовать, нужно выполнить компиляцию с Babel (или чем-то подобным).
const food = { beef: '🥩', bacon: '🥓' }; const cloneFood = { ...food }; console.log(cloneFood); // { beef: '🥩', bacon: '🥓' }
Использование Object.assign
Object.assign, выпущенный официально, также создает неглубокую копию объекта.
const food = { beef: '🥩', bacon: '🥓' }; const cloneFood = Object.assign({}, food); console.log(cloneFood); // { beef: '🥩', bacon: '🥓' }
Использование JSON
Этот способ предоставляет глубокую копию. Стоит упомянуть, что это быстрый и грязный способ глубокого клонирования объекта. В качестве более надежного решения рекомендуется использовать что-то вроде lodash.
const food = { beef: '🥩', bacon: '🥓' }; const cloneFood = JSON.parse(JSON.stringify(food)) console.log(cloneFood); // { beef: '🥩', bacon: '🥓' }
Lodash DeepClone или JSON?
- JSON.stringify/parse работает только с литералом Number, String и Object без функции или свойства Symbol.
- deepClone работает со всеми типами, а функция и символ копируются по ссылке.
Пример:
const lodashClonedeep = require("lodash.clonedeep"); const arrOfFunction = [() => 2, { test: () => 3, }, Symbol('4')]; // копия deepClone по ссылочной функции и символу console.log(lodashClonedeep(arrOfFunction)); // JSON заменяет функцию на ноль и функцию в объекте на undefined console.log(JSON.parse(JSON.stringify(arrOfFunction))); // функция и символ копируются по ссылке в deepClone console.log(lodashClonedeep(arrOfFunction)[0] === lodashClonedeep(arrOfFunction)[0]); console.log(lodashClonedeep(arrOfFunction)[2] === lodashClonedeep(arrOfFunction)[2]);
Глубокое или неглубокое клонирование?
При использовании spread для копирования объекта создается неглубокая копия. Если массив является вложенным или многомерным, этот способ не будет работать. Рассмотрим пример:
const nestedObject = { country: '🇨🇦', { city: 'vancouver' } }; const shallowClone = { ...nestedObject }; // Изменение клонированного объекта clonedNestedObject.country = '🇹🇼' clonedNestedObject.country.city = 'taipei';
Таким образом, клонированный объект был изменен с добавлением city. В результате получаем:
console.log(shallowClone); // {country: '🇹🇼', {city: 'taipei'}} <-- ✅ console.log(nestedObject); // {country: '🇨🇦', {city: 'taipei'}} <-- 😱
Неглубокая копия предполагает копирование первого уровня и ссылается на более глубокие уровни.
Глубокая копия
Возьмем тот же пример, но применим глубокую копию с использованием JSON:
const deepClone = JSON.parse(JSON.stringify(nestedObject)); console.log(deepClone); // {country: '🇹🇼', {city: 'taipei'}} <-- ✅ console.log(nestedObject); // {country: '🇨🇦', {city: 'vancouver'}} <-- ✅
Глубокая копия является копией для вложенных объектов. Однако иногда достаточно использования неглубокой копии.
Производительность
К сожалению, на данный момент нельзя написать тестирование для spread, поскольку официально он не указан в спецификации. Однако результат показывает, что Object.assign намного быстрее, чем JSON. Тест на производительность можно найти здесь.
Object.assign и Spread
Стоит отметить, что Object.assign — это функция, которая модифицирует и возвращает целевой объект. В данном примере при использовании:
const cloneFood = Object.assign({}, food)
{} — это модифицируемый объект. В этой точке на целевой объект не ссылаются никакие переменные, но поскольку Object.assign возвращает целевой объект, то можно сохранить полученный присвоенный объект в переменную cloneFood. Данный пример можно изменить следующим образом:
const food = { beef: '🌽', bacon: '🥓' }; Object.assign(food, { beef: '🥩' }); console.log(food); // { beef: '🥩', bacon: '🥓' }
Очевидно, что значение beef в объекте food неверно, поэтому нужно назначить правильное значение beef с помощью Object.assign. На самом деле мы не используем возвращаемое значение функции, а изменяем целевой объект, на который ссылаемся с помощью константы food.
С другой стороны, Spread — это оператор, который копирует свойства одного объекта в новый объект. При репликации приведенного выше примера с помощью spread для изменения переменной food…
const food = { beef: '🌽', bacon: '🥓' }; food = { ...food, beef: '🥩', } // TypeError: invalid assignment to const `food'
… мы получаем ошибку в результате использования spread для создания новых объектов и, следовательно, присваиваем новый объект для food, который был объявлен с помощью const, что недопустимо. Поэтому можно либо объявить новую переменную для хранения нового объекта, как показано ниже:
const food = { beef: '🌽', bacon: '🥓' }; const newFood = { ...food, beef: '🥩', } console.log(newFood); // { beef: '🥩', bacon: '🥓' }
либо объявить food с let или var, что позволит присвоить новый объект:
let food = { beef: '🌽', bacon: '🥓' }; food = { ...food, beef: '🥩', } console.log(food); // { beef: '🥩', bacon: '🥓' }
Спасибо за внимание!
Специально для сайта ITWORLD.UZ. Новость взята с сайта NOP::Nuances of programming