JavaScript — глубокое копирование массива

В этом посте давайте разберемся с различными вариантами копирования, доступными нам в JavaScript.

Допустим, у нас есть массив [1, 2, 3, 4, 5] и нам нужно сделать его копию. Первое, что мы можем попробовать, это метод concat, как показано ниже.

const numbers = [1, 2, 3, 4, 5];
const clone = [].concat(fibonacci);
console.log(clone) // [1, 2, 3, 4, 5]

Это работает, но вы также можете увидеть странный синтаксис конкатенации с пустым массивом. В любом случае, это делает работу.

Но с ES6 у нас также есть возможность использовать оператор распространения, как показано ниже.

const numbers = [1, 2, 3, 4, 5];
const clone = [...numbers];
console.log(clone); // [1, 2, 3, 4, 5]

В этом случае код выглядит более элегантно и выразительно. Мы можем выразить код на простом английском языке как — мы хотим распространить элементы массива чисел в новый массив клонов. Другое преимущество, которое у нас есть, заключается в том, что вы можете смешивать и сочетать, как показано ниже.

const firstTwo = [1, 2];
const lastTwo = [4, 5];
const completeArray = [...firstTwo, 3, ...lastTwo];
console.log(completeArray); // [1, 2, 3, 4 ,5]

Как мы видим, мы также можем распределить несколько элементов в массиве. Теперь давайте возьмем случай редактирования

const numbers = [1, 2, 3, 4, 5];
const clone = [...numbers];
console.log(clone); // [1, 2, 3, 4, 5]
clone[0] = 10;
console.log(clone); // [10, 2, 3, 4, 5]
console.log(numbers); // [1, 2, 3, 4, 5]

Как мы видим, хотя мы обновляем первый элемент в клонированном массиве, это не влияет на исходный массив чисел. Что говорит о том, что оно действительно было скопировано.

Но допустим, нам нужно клонировать [[1, 2], 3, [4, 5]].

const array = [[1, 2], 3, [4, 5]];
const clone = [...array]
console.log(clone); // [[1, 2], 3, [4, 5]]

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

const array = [[1, 2], 3, [4, 5]];
const clone = [...array]
console.log(clone); // [[1, 2], 3, [4, 5]]

clone[0][1] = 10;
console.log(clone); // What do you think this will print?
console.log(array); // What do you think this will print?

Как и ожидалось, печать клона печатает [[1, 10], 3, [4, 5]] . Но как насчет печати переменной массива, из которого были скопированы данные? Если вы уже распечатали, вы удивлены? Ок, результат печати массива [[1, 10], 3, [4, 5]] as well. Теперь вроде не копировался и ссылка есть.

Но если вы сейчас попытаетесь изменить clone[1] на любое значение, это не повлияет на исходный массив. и так, что здесь происходит? Это копирование или ссылка?

На самом деле, когда мы объединяем другой массив в массив, его ссылка копируется, в отличие от примитивов, где копируется значение. То же самое относится и к объектам JavaScript. Таким образом, по умолчанию происходит неглубокое копирование.

Теперь вы можете подумать о старом методе concat, который мы видели в первом примере. К сожалению, он также следует тому же принципу поверхностного копирования. Хорошо, так как же нам тогда глубоко скопировать массив?

Если вы не хотите создавать собственное решение, то, конечно, вы можете воспользоваться методами lodash. Но давайте посмотрим, как это сделать самостоятельно.

Первый простой и грязный подход — использовать строку JSON, как показано ниже.

const numbers = [[1, 2], 3, [4, 5]];
const clone = JSON.parse(JSON.stringify(numbers));
console.log(clone); // [[1, 2], 3, [4, 5]]
clone[0][1] = 10;
console.log(clone); // [[1, 10], 3, [4, 5]]
console.log(numbers); // [[1, 2], 3, [4, 5]]

Но JSON stringify не является эффективным подходом, поэтому давайте создадим собственную функцию для копирования массива, как показано ниже.

function arrayDeepCopy(array) {
  let newArray = [];

  array.forEach(element => {
    if (Array.isArray(element)) {
      newArray.push(arrayDeepCopy(element));
    } else {
      newArray.push(element);
    }
  });

  return newArray;
}

const numbers = [[1, 2], 3, [4, 5]];
const clone = arrayDeepCopy(numbers);
console.log(clone); // [[1, 2], 3, [4, 5]]
clone[0][1] = 10;
console.log(clone); // [[1, 10], 3, [4, 5]]
console.log(numbers); // [[1, 2], 3, [4, 5]]

Как видите, мы создаем функцию, в которую передаем массив, который хотим глубоко скопировать. Затем мы перебираем каждый элемент, а затем проверяем, является ли элемент массивом, используя метод Array.isArray javascript. Если это массив, то мы рекурсивно вызываем ту же функцию глубокого копирования. Если это не массив, то мы нажимаем на переменную newArray и, наконец, возвращаем ее, и она работает так, как ожидалось.

Дополнительная информация

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

Таким образом, мы увидели, как выполнить глубокую копию массива.

Спасибо за прочтение!