Поскольку объекты в #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 для клонирования объектов находился на стадии 4, соответственно официально он не указан в спецификациях. Поэтому для того, чтобы его использовать, нужно выполнить компиляцию с Babel (или чем-то подобным).
const food = { beef: '????', bacon: '????' };
const cloneFood = { ...food };
console.log(cloneFood);
// { beef: '????', bacon: '????' }
Object.assign
, выпущенный официально, также создает неглубокую копию объекта.
const food = { beef: '????', bacon: '????' };
const cloneFood = Object.assign({}, food);
console.log(cloneFood);
// { beef: '????', bacon: '????' }
Этот способ предоставляет глубокую копию. Стоит упомянуть, что это быстрый и грязный способ глубокого клонирования объекта. В качестве более надежного решения рекомендуется использовать что-то вроде lodash.
const food = { beef: '????', bacon: '????' };
const cloneFood = JSON.parse(JSON.stringify(food))
console.log(cloneFood);
// { beef: '????', bacon: '????' }
Пример:
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 — это функция, которая модифицирует и возвращает целевой объект. В данном примере при использовании:
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: '????' }
Спасибо за внимание!
Перевод статьи Paulo Gomes: 4 golang code snippets that will deceive C# developers!
Комментарии