Кратко
Секция статьи "Кратко"При копировании объектов или массивов JavaScript копирует данные только на один уровень вглубь. Этот тип копирования называется поверхностным (shallow).
Если необходимо полностью скопировать сложную структуру данных, например, массив с объектами, то нужно делать глубокое (deep) или полное копирование данных. JavaScript не содержит функций для глубокого копирования, лучший вариант сделать глубокую копию — сериализовать структуру в JSON и тут же распарсить.
Как понять
Секция статьи "Как понять"Проблема поверхностного копирования
Секция статьи "Проблема поверхностного копирования"Поверхностное копирование работает быстро и в большинстве случаев его достаточно. Проблемы появляются, когда приходится копировать вложенные структуры:
const itemsInCart = [ { product: 'Носки', quantity: 3 }, { product: 'Штаны', quantity: 1 }, { product: 'Кепка', quantity: 1 },]const clonedCart = [...itemsInCart]
const itemsInCart = [ { product: 'Носки', quantity: 3 }, { product: 'Штаны', quantity: 1 }, { product: 'Кепка', quantity: 1 }, ] const clonedCart = [...itemsInCart]
Если изменять элементы этой структуры после копирования, то эти изменения будут также видны в исходной структуре:
clonedCart[1].quantity = 5console.log(clonedCart)// [// { product: 'Носки', quantity: 3 },// { product: 'Штаны', quantity: 5 },// { product: 'Кепка', quantity: 1 },// ]console.log(itemsInCart)// [// { product: 'Носки', quantity: 3 },// { product: 'Штаны', quantity: 5 },// { product: 'Кепка', quantity: 1 },// ]
clonedCart[1].quantity = 5 console.log(clonedCart) // [ // { product: 'Носки', quantity: 3 }, // { product: 'Штаны', quantity: 5 }, // { product: 'Кепка', quantity: 1 }, // ] console.log(itemsInCart) // [ // { product: 'Носки', quantity: 3 }, // { product: 'Штаны', quantity: 5 }, // { product: 'Кепка', quantity: 1 }, // ]
Непримитивные типы данных, такие как массивы и объекты, хранятся по ссылке. Так как копирование происходит только на один уровень вглубь, то при копировании массива происходит копирование ссылок на старые объекты в новый массив.
В итоге получается картина, когда разные массивы ссылаются на одни и те же объекты в памяти:
console.log(itemsInCart[1] === clonedCart[1])// true
console.log(itemsInCart[1] === clonedCart[1]) // true
Как получить глубокую копию
Секция статьи "Как получить глубокую копию"JavaScript не содержит отдельных функций для глубокого копирования массивов или объектов. Существуют различные способы сделать глубокое копирование.
Можно написать функцию глубокого копирования вручную. Скорее всего ваша функция будет рекурсивной, и она будет работать только для конкретных данных — написать универсальную функцию не так-то просто.
Можно воспользоваться готовой библиотекой. Например, функцию глубокого копирования содержит популярная библиотека утилит lodash. Функция гарантировано работает в подавляющем большинстве случаев, потому что используется в десятках тысяч проектов каждый день. Исходный код библиотеки открыт, можно изучить исходный код функции глубокого копирования.
import cloneDeep from 'lodash.clonedeep'const deep = cloneDeep(itemsInCart)console.log(itemsInCart[1] === deep[1])// false
import cloneDeep from 'lodash.clonedeep' const deep = cloneDeep(itemsInCart) console.log(itemsInCart[1] === deep[1]) // false
Самый быстрый способ глубокого копирования звучит глупо — нужно сериализовать копируемый объект в JSON и тут же распарсить его. В результате появится полная копия объекта:
const deep = JSON.parse(JSON.stringify(itemsInCart))console.log(itemsInCart[1] === deep[1])// false
const deep = JSON.parse(JSON.stringify(itemsInCart)) console.log(itemsInCart[1] === deep[1]) // false
У этого метода есть ограничение — копируемые данные должны быть сериализуемыми. Если объект содержит методы или массив содержит функции, то копирование с помощью JSON-преобразования не сработает:
const fns = [ function() { console.log('aaa') }, function() { console.log('bbb') },]const copyFns = JSON.parse(JSON.stringify(fns))console.log(copyFns)// [null, null]
const fns = [ function() { console.log('aaa') }, function() { console.log('bbb') }, ] const copyFns = JSON.parse(JSON.stringify(fns)) console.log(copyFns) // [null, null]