Рубрики
JavaScript

Копировать массив или объект JS по правилам

Подробный разбор правильного копирования массивов и объектов с примерами кода на JavaScript.

Как только вы решили копировать массив или объект в 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))

Подводя итог

  1. Помните — объекты Javascript изменчивы и хранятся в памяти по ссылке;
  2. Все классические методы клонирования или Spread syntax осуществляют поверхностное копирование(на одном уровне);
  3. JSON.stringify и JSON.parse можно использовать для глубокого копирования;
  4. Можно создать свою собственную функцию для глубокого копирования или использовать сторонние библиотеки, такие как Lodash, Underscore и тп.

Статья подготовлена по материалам следующих источников: