В джаваскрипте можно взять любой объект и поменять в нем какое-нибудь значение.

const obj = { a: 1, b: 2, c: 3 }
obj.b = 100
    
console.log(obj)
> { a: 1, b: 100, c: 3 };

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

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

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

const obj = { a: 1, b: 2, c: 3 }
const objCopy = obj
    
objCopy.c = 100
    
console.log(objCopy)
> { a: 1, b: 2, c: 100 } // то, что и хотели
    
console.log(obj)
> { a: 1, b: 2, c: 100 } // свойство "c" в исходном объекте тоже изменилось

Исходный объект тоже почему-то поменялся, хотя его никто, казалось бы, не трогал. Дело в том, что objCopy тут на самом деле никакая не копия. В первой строчке, при объявлении obj где-то в недрах памяти создается объект, на который идентификатор obj будет ссылаться каждый раз, когда потребуется узнать его значение. Строчка objCopy = obj не создает новый объект, как можно было бы предположить, а создает новый идентификатор, который ссылается на тот же, уже существующий объект. В результате, при изменении любого из этих значений, будут меняться оба.

Как тогда сделать настоящую копию вместо ссылки на объект? Можно попробовать спред:

const obj = { a: 1, b: 2, c: 3 }
const objCopy = { ...obj }
    
objCopy.c = 100
    
console.log(objCopy)
> { a: 1, b: 2, c: 100 }
    
console.log(obj)
> { a: 1, b: 2, c: 3 } // сработало!

На самом деле нет. Спред копирует значение только на первом уровне, а дальше опять будут ссылки. Это называется shallow copy. Со вложенными объектами такой способ не работает:

const obj = { a: 1, b: { c: 3 } }
const objCopy = { ...obj }
    
objCopy.b.c = 100
    
console.log(objCopy)
> { a: 1, b: { c: 100 } }
    
console.log(obj)
> { a: 1, b: { c: 100 } }

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

const objCopy = JSON.parse(JSON.stringify(obj))

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

Хорошая новость в том, что всегда можно написать при необходимости функцию для копирования объекта самостоятельно или воспользоваться уже готовой из какой-нибудь библиотеки. Ну или писать, где можно, в неизменяемом стиле, чтобы не приходилось задумываться над такого рода проблемами.