Если вы новичок в JavaScript и пытаетесь клонировать объект, вы можете не получить ожидаемых результатов. Даже если вы знаете, как клонировать объекты, вам может понадобиться узнать, что существует несколько способов клонирования объектов с некоторыми отличиями. В этой статье мы сосредоточимся на глубоком клонировании объекта и пройдемся по всем методам глубокого клонирования и их различиям.

Как я упоминал ранее, мы сосредоточимся на глубоком клонировании объектов, но перед этим позвольте мне кратко объяснить два типа клонирования объектов.

Поверхностное копирование:

При неглубоком клонировании объекта Javascript будет копировать значения примитивных типов данных, таких как строки, числа и логические значения, на первом уровне. JavaScript будет копировать только ссылку на объекты и массивы на вложенных уровнях.

Глубокое копирование:

При глубоком клонировании объекта Javascript будет копировать значения, а не ссылки на все примитивы и непримитивы на каждом уровне.

Возьмем пример:

let user = {
  name: 'Zuhair',
  number: 123456789,
  address: {
    city: 'Karachi',
    zipCode: 7800
  }
};

//Shallow copy
let shallowCopy = user;

//Mutating the primitives on first level
shallowCopy.name = 'Farhan';
shallowCopy.number = 987654321;

//Mutating the referenced data type(Object) on nested level
shallowCopy.address.city = 'Islamabad';

console.log(user);
/*
Output of user
{
    "name": "Farhan",
    "number": 987654321,
    "address": {
        "city": "Islamabad",
        "zipCode": 7800
    }
}
*/

В приведенном выше случае вы можете видеть, что мы присвоили пользовательский объект переменной smallCopy, затем мы изменили имя, номер и город, вложенные в адресный объект, и зарегистрировали пользовательскую переменную, и мы можем видеть, что Javascript изменил фактический пользовательский объект, потому что JavaScript не копировал фактические значения пользовательского объекта в переменную smallCopy, а присваивал ссылку на пользовательский объект. Поэтому, когда мы обновляем мелкое копирование, оно фактически мутирует исходный пользовательский объект из-за ссылки.

Позвольте мне подытожить:
Предположим, мы поверхностно клонируем объект и меняем его значения. Он изменит значение исходного объекта, потому что всякий раз, когда вы создаете поверхностную копию объекта или массива, он будет хранить ссылку на этот объект или массив, а не фактические значения.

Теперь давайте подробно поговорим о различных способах глубокого клонирования и их различиях.

1. Использование метода Object.assign()

Метод Object.assign() принимает два параметра: target и sources, где target — целевой объект, а sources — объекты, которые вы хотите скопировать. Подробнее об этом методе можно узнать здесь.

Этот метод скопирует все, включая методы внутри объекта, но помните, что этот метод клонирования частично копирует объект. Возьмем пример.

let user = {
  name: 'Zuhair',
  address: {
    city: 'Karachi',
    zipCode: 7800
  },
  printName: function(){
    console.log(this.name);
  }
};

let clonedUser = Object.assign({}, user);

clonedUser.name = 'Farhan';

clonedUser.address.city = 'Islamabad';

console.log(user);

/*
{
    "name": "Zuhair",
    "address": {
        "city": "Islamabad",
        "zipCode": 7800
    },
    printName: ƒ
}
*/

В приведенном выше примере мы клонируем объект пользователя с помощью метода Object.assign() и присваиваем вновь созданный объект переменной clonedUser, а затем меняем значение имени и города внутри вложенного адреса. объект. Когда мы регистрируем пользовательский объект, мы видим, что имя по-прежнему не изменено, но город изменен, потому что метод Object.assign() копирует примитивы(строки, числа и логические значения) по значениям, поэтому в этом случае свойство имени является примитивным и копируется значением, поэтому, когда мы изменили свойство имени из объекта clonedUser, свойство имени объекта пользователя не изменилось, а с другой стороны, объект адреса внутри пользовательского объекта изменено, потому что объект и массивы имеют ссылочный тип, а ссылка на адрес хранится в clonedUser, а не в исходном значении.

2. Использование JSON.stringify() и JSON.parse()

Метод JSON.stringify() принимает объект в качестве параметра и преобразует этот объект в строку, а метод JSON.parse() принимает действительный объект в строковой форме как параметр и преобразует его в объект.

let user = {
  name: 'Zuhair',
  number: 123456789,
  address: {
    city: 'Karachi',
    zipCode: 7800
  }
};

let clonedUser = JSON.parse(JSON.stringify(user));

clonedUser.name = 'farhan'
clonedUser.address.city = 'Islamabad'

console.log(user);
/*
{
    "name": "Zuhair",
    "number": 123456789,
    "address": {
        "city": "Karachi",
        "zipCode": 7800
    }
}
*/

В приведенном выше примере мы клонировали пользовательский объект, преобразовав его в строку и затем проанализировав его. Вот как мы глубоко клонировали объект пользователя и присвоили его переменной clonedUser. Чтобы убедиться, что наш новый клонированный объект является глубокой копией объекта пользователя, мы изменили имя и адрес объекта clonedUser, а затем зарегистрировали объект пользователя. Наш пользовательский объект не поврежден, хотя мы изменили клонированный объект, потому что создали глубокую копию. В данном случае мы клонировали вложенный объект адреса по значению, а не по ссылке, поэтому он тоже не изменился.

Но у нас есть одна проблема с этим методом; на данный момент у нас нет никаких методов внутри объекта, поэтому он работает нормально, но если у нас есть методы в объекте, этот способ копирования объекта не будет копировать эти методы.

let user = {
  name: 'Zuhair',
  printName: function(){
    console.log(this.name);
  }
};

console.log(user); //{name: 'Zuhair', printName: ƒ}

let clonedUser = JSON.parse(JSON.stringify(user));

console.log(clonedUser); //{name: 'Zuhair'}

Вы можете видеть, что пользовательский объект имеет метод printName, но когда мы регистрируем клонированный объект, метод printName отсутствует.

Примечание: если пользовательский объект имеет какой-либо метод, определенный в прототипе, он будет клонирован.

3. Использование оператора спреда ES6

Используя три точки (…), мы можем копировать значения объекта.

Возьмем пример.

let user = {
  name: 'Zuhair',
  address: {
    city: 'Karachi',
    zipCode: 7800
  },
  printName: function(){
    console.log(this.name);
  }
};

let clonedUser = {...user};

clonedUser.name = 'Farhan';

clonedUser.address.city = 'Islamabad';

console.log(user);

/*
{
    "name": "Zuhair",
    "address": {
        "city": "Islamabad",
        "zipCode": 7800
    },
    printName: ƒ
}
*/

Клонирование с помощью оператора распространения почти не отличается от метода Object.assign(). Это делает частичную копию объекта, что означает, что любой объект, вложенный в объект, не будет глубоко клонирован. Если вы зарегистрируете clonedUser, вы также найдете метод printName.

4. Использование структурированного клона ()

Это новый нативный метод, представленный в браузерах. Впервые он был представлен в NodeJS 11 экспериментально. Он принимает объект в качестве параметра и возвращает глубокую копию этого объекта.

let user = {
  name: 'Zuhair',
  address: {
    city: 'Karachi',
    zipCode: 7800
  }
};

let clonedUser = structuredClone(user);

clonedUser.name = 'Farhan';

clonedUser.address.city = 'Islamabad';

console.log(user);
/*
{
  name: 'Zuhair',
  address: {
    city: 'Karachi',
    zipCode: 7800
  }
}
*/

Вы можете видеть, что мы изменили объект clonedUser, но исходный объект пользователя не изменился, и вложенный адресный объект также глубоко клонирован.

Этот метод является новым и может работать не во всех браузерах. Это стандартный способ глубокого клонирования объекта, но у нас есть проблема с этим методом. Если мы попытаемся скопировать объект, внутри которого есть методы, он выдаст ошибку.

Заключение

Как мы видим, существует много разных способов клонирования объектов, но каждый метод работает по-своему. При клонировании объектов мы должны знать эти различия:

Оператор присваивания (=):Используя оператор присваивания, мы можем создать поверхностную копию объекта.

Object.assign(): этот метод создаст частичную глубокую копию объекта, включая его методы.

JSON.stringify() и JSON.parse():используя этот метод, мы можем создать глубокую копию объекта, не включая методы внутри объекта.

Оператор спреда ES6. Используя оператор спреда, мы можем частично клонировать объект, и он также будет включать методы внутри объекта.

structuredClone(): Используя этот метод, мы можем создать глубокую копию, но если объект содержит какой-либо метод внутри, он выдаст ошибку.