ES6: Как клонировать объект в javascript?

Привет народ👋 надеюсь у вас все хорошо.
Вы, наверное, думаете: «Клонировать объект»? Что в этом такого?
Ну, я тоже так думал, пока не столкнулся с проблемой, которая заняла у меня 2 дня, чтобы отладить, что я сделал что-то ужасное с клонированием объекта.

Итак, давайте посмотрим, как мы можем клонировать объекты в javascript.

// we have a user object
const user = {
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com"
} 
Вход в полноэкранный режим Выйти из полноэкранного режима

Теперь, если мы хотим скопировать этот пользовательский объект, все просто!

const copiedUser = user;
Войти в полноэкранный режим Выйти из полноэкранного режима

Легко, да?… ну это худший способ копирования пользователя, очевидно, что у вас есть некоторые неправильные представления о том, что делает оператор const copiedUser = user;.

В JavaScript объекты передаются и присваиваются по ссылке (точнее, по значению ссылки), поэтому user и copiedUser — это ссылки на один и тот же объект.

// [Object1]<--------- user

const copiedUser = user;

// [Object1]<--------- user
//         ^ 
//         |
//         ----------- copiedUser
Вход в полноэкранный режим Выход из полноэкранного режима

Как видно после присваивания, обе ссылки указывают на один и тот же объект.

const user = {
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com"
}  
const copiedUser = user;
copiedUser.name = "XYZ"
console.log(copiedUser) // {name:"XYZ",email:"st.deepak15@gmail.com"}
console.log(user) // {name:"XYZ",email:"st.deepak15@gmail.com"}
Вход в полноэкранный режим Выход из полноэкранного режима

Изменение любой из них приведет к изменению обеих 🙁

Как же тогда создать копию, если нам нужно изменить один объект, а не другой?

1. Оператор разворота

const spreadUser = {...user}
spreadUser.name = "XYZ"
console.log(spreadUser) // {name:"XYZ",email:"st.deepak15@gmail.com"}
console.log(user) // {name:"Deepak Negi",email:"st.deepak15@gmail.com"}

Войти в полноэкранный режим Выйти из полноэкранного режима

2. Object.assign()

const assignUser = Object.assign({}, user);
assignUser.name = "XYZ"
console.log(assignUser) // {name:"XYZ",email:"st.deepak15@gmail.com"}
console.log(user) // {name:"Deepak Negi",email:"st.deepak15@gmail.com"}

Войти в полноэкранный режим Выход из полноэкранного режима

Наконец-то мы разобрались!

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

const user = {
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"New Delhi",
    state:"Delhi",
    zipcode: 000000,
    country:"India"
  }
}
const spreadUser = {...user}
spreadUser.address.city = "Pune"
spreadUser.address.state = "Mumbai"

console.log(spreadUser)
// console output 
{
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"Pune",
    state:"Mumbai",
    zipcode: 000000,
    country:"India"
  }
}

console.log(user)
// console output 
{
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"Pune",
    state:"Mumbai",
    zipcode: 000000,
    country:"India"
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

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

Но почему?
Из-за неглубокого копирования, т.е. оператор object spread, как и Object.assign, не клонирует значения вложенных объектов, а копирует ссылку на вложенный объект. Это называется неглубоким копированием.

Что же нам тогда делать? Глубокое копирование?
Да, Deep copy/Deep clone будет копировать объект, даже вложенные свойства, для этого сериализуйте объект в JSON и разберите его обратно в JS объект.

const user = {
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"New Delhi",
    state:"Delhi",
    zipcode: 000000,
    country:"India"
  }
}
const deepCopiedUser = JSON.parse(JSON.stringify(user))

deepCopiedUser.address.city = "Pune"
deepCopiedUser.address.state = "Mumbai"

console.log(deepCopiedUser)
// console output 
{
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"Pune",
    state:"Mumbai",
    zipcode: 000000,
    country:"India"
  }
}

console.log(user)
// console output 
{
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"New Delhi",
    state:"Delhi",
    zipcode: 000000,
    country:"India"
  }
}

Войти в полноэкранный режим Выход из полноэкранного режима

Итак, теперь наш оригинальный объект user не изменяется, когда мы изменяем deepCopiedUser.

Остерегайтесь использовать JSON.parse(JSON.stringify(user)), если вы имеете дело с датой, функциями, RegExps, Maps, Sets или другими сложными типами внутри вашего объекта. Метод JSON не может работать с такими типами.

Поэтому для таких случаев, вероятно, лучше всего подойдет метод lodash clonedeep.

import {cloneDeep} from 'lodash'
or
const {cloneDeep} = require('lodash')

const user = {
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"New Delhi",
    state:"Delhi",
    zipcode: 000000,
    country:"India"
  }
}
const deepCloneUser = cloneDeep(user)
deepCloneUser.address.city = "Pune"
deepCloneUser.address.state = "Mumbai"

console.log(deepCloneUser)
// console output 
{
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"Pune",
    state:"Mumbai",
    zipcode: 000000,
    country:"India"
  }
}

console.log(user)
// console output 
{
  name:"Deepak Negi",
  email:"st.deepak15@gmail.com",
  address:{
    line1:"ABC, Tower X",
    city:"New Delhi",
    state:"Delhi",
    zipcode: 000000,
    country:"India"
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Наконец-то!!!

Дайте мне знать в комментариях, какой, по вашему мнению, лучший способ глубокого клонирования объекта.

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *