Как только вы решили копировать массив или объект в JavaScript следует вспомнить о том, что массивы и объекты являются изменяемыми(mutable), в отличие от примитивных переменных и хранятся как ссылки.
Изменяемые, это те, состояние которых может быть изменено после их создания.
Изменяемые
(Mutable):
- object
- array
- function
Неизменяемые
(Immutable):
- string
- number
- boolean
- null
- undefined
- symbol
На практике это будет означать следующее:
const sheeps = ['🐑', '🐑']
const sheeps2 = sheeps
sheeps2.push('🐺')
console.log(sheeps2)
// [ '🐑', '🐑', '🐺' ]
// Аааа 😱, оригинальный массив sheeps изменился?!
console.log(sheeps)
// [ '🐑', '🐑', '🐺' ]
Поэтому необходимо создавать копию данных, а не делать ссылку на существующие.
Копировать массив или объект в JS не так просто как кажется
Для этого необходимо не просто использовать знак равенства для присвоения новой переменной старых значений, а производить клонирование, иначе вы просто создадите ссылку и будете работать с исходным объектом.
Использовать функцию slice() для копирования массива
Эта функция используется для копирования части массива. Если не указывать параметры, то массив будет копироваться целиком.
const sheeps = ['🐑', '🐑', '🐑']
const cloneSheeps = sheeps.slice()
Другой способ с использованием Array.from
:
const cloneSheeps = Array.from(sheeps)
Если бы sheeps
был бы объектом, то копировать следовало бы так:
const cloneSheeps = {}
cloneSheeps.prototype = sheeps.prototype
Или так:
let cloneSheeps = Object.assign({}, sheeps)
Использовать Spread syntax чтобы копировать массив или объект
Spread syntax, появившийся в ES6, позволяет «вытаскивать» перебираемые элементы из своего контейнера.
const sheeps = ['🐑', '🐑', '🐑'];
const fakeSheeps = sheeps;
// три точки - это и есть spread operator
const cloneSheeps = [...sheeps];
console.log(sheeps === fakeSheeps);
// true --> это указывает на тот же кусок в памяти
console.log(sheeps === cloneSheeps);
// false --> это указывает на новый кусок в памяти
Неглубокое копирование
Обратите внимание на то, что происходит клонирование только одного уровня — поверхностное копирование!
Если у вас многомерный массив для создания копии каждого уровня придется применить один из предыдущих способов к каждому уровню отдельно.
Например так:
let sheeps = [['🐑', '🐑'], ['🐑', '🐑'], ['🐑', '🐑']]
let cloneSheeps = []
for (let i = 0; i < sheeps.length; i++)
cloneSheeps.push(sheeps[i].slice())
cloneSheeps[1].push('🐺')
console.log(sheeps)
console.log(cloneSheeps)
Или с помощью функции .map()
:
let sheeps = [['🐑', '🐑'], ['🐑', '🐑'], ['🐑', '🐑']]
let cloneSheeps = sheeps.map(function (item) {
return [...item]
})
cloneSheeps[1].push('🐺')
console.log(sheeps)
console.log(cloneSheeps)
Если у вас смешанные данные, например, когда массив содержит объекты, в которых значениями могут быть тоже объекты или массивы, то следует использовать рекурсивную функцию, в которой вы будете проверять тип данных и в зависимости от этого копировать массив, объект или примитивную переменную.
Пример:
function deepCopy (obj) {
if ('object' === typeof obj) {
if (obj instanceof Array) {
let length = obj.length
let newObj = new Array(length)
for (let i = 0; i < length; i++) {
newObj[i] = (deepCopy(obj[i]))
}
return newObj
} else {
let newObj = {}
if (obj.prototype) {
newObj.prototype = obj.prototype
}
for (let key in obj) {
newObj[key] = deepCopy(obj[key])
}
return newObj
}
}
return obj
}
Лучший способ клонировать многомерный массив или объект
На данный момент наиболее простым способом клонирования массивов и объектов является преобразование данных в строку, а за тем обратное преобразование в объект с помощью JSON
:
let cloneSheeps = JSON.parse(JSON.stringify(sheeps))
Подводя итог
- Помните — объекты Javascript изменчивы и хранятся в памяти по ссылке;
- Все классические методы клонирования или Spread syntax осуществляют поверхностное копирование(на одном уровне);
JSON.stringify
иJSON.parse
можно использовать для глубокого копирования;- Можно создать свою собственную функцию для глубокого копирования или использовать сторонние библиотеки, такие как Lodash, Underscore и тп.
Статья подготовлена по материалам следующих источников: