Avatar ·

Как создать копию массива в JavaScript, а не ссылку на него?

Всем привет! Я новичок в JavaScript и столкнулся с проблемой, которую никак не могу решить. Когда я пытаюсь скопировать массив, то изменения в новом массиве влияют на исходный. Вот пример: ```javascript let original = [1, 2, 3]; let copy = original; copy.push(4); console.log(original); // [1, 2, 3, 4] — я не хочу, чтобы original менялся! ``` Я понимаю, что `copy = original` создает не копию, а ссылку на тот же массив. Но как сделать так, чтобы `copy` был полностью независимым? Я пробовал `copy = original.slice()`, и это сработало для простых чисел. Но когда у меня вложенные массивы или объекты, изменения всё равно затрагивают оригинал: ```javascript let nested = [[1, 2], [3, 4]]; let copyNested = nested.slice(); copyNested[0].push(5); console.log(nested[0]); // [1, 2, 5] — опять изменился! ``` В чём дело? Как сделать "глубокую" копию, чтобы любые изменения в копии не трогали оригинал? Желательно с примерами для разных случаев: простые массивы, массивы объектов, многомерные массивы. Заранее спасибо! <<>>

Проблема: ссылки, а не копии

В JavaScript массивы и объекты передаются по ссылке. Когда вы пишете let copy = original, вы просто создаёте новую переменную, указывающую на тот же участок памяти. Чтобы создать независимую копию, нужно использовать методы копирования.

Поверхностное копирование (shallow copy)

Для одномерных массивов с примитивными значениями (числа, строки, булевы) подходят:

  • Array.slice() — let copy = original.slice();
  • spread-оператор — let copy = [...original];
  • Array.from() — let copy = Array.from(original);
  • concat() — let copy = [].concat(original);

Пример:

let arr = [1, 2, 3];
let copy = [...arr];
copy.push(4);
console.log(arr); // [1, 2, 3] — оригинал не изменился

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

Глубокое копирование (deep copy)

Для полной независимости нужно рекурсивно скопировать все уровни вложенности. Вот основные способы:

1. JSON.parse + JSON.stringify (простой, но с ограничениями)

let original = [{a: 1, b: {c: 2}}];
let deepCopy = JSON.parse(JSON.stringify(original));

deepCopy[0].b.c = 999;
console.log(original[0].b.c); // 2 — не изменилось!

Минусы: Не копирует функции, undefined, символы, даты (превращает в строки), не работает с циклическими ссылками.

2. Рекурсивная функция (полный контроль)

function deepClone(obj) {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof Array) {
    return obj.map(item => deepClone(item));
  }
  if (obj instanceof Object) {
    let cloned = {};
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        cloned[key] = deepClone(obj[key]);
      }
    }
    return cloned;
  }
}

let nested = [[1, 2], {x: 10}];
let cloned = deepClone(nested);
cloned[0].push(5);
console.log(nested[0]); // [1, 2] — оригинал не тронут

3. structuredClone (современный браузерный API)

let original = [{a: 1, b: new Date()}];
let copy = structuredClone(original);
copy[0].a = 100;
console.log(original[0].a); // 1

Поддерживается в современных браузерах и Node.js 17+. Копирует большинство типов, включая даты, Map, Set, но не функции и DOM-узлы.

Итог

  • Для простых массивов — slice() или spread.
  • Для вложенных структур — JSON.parse(JSON.stringify()) (если нет спецтипов) или structuredClone.
  • Для полного контроля — напишите свою рекурсивную функцию.

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

<<>>

Альтернативный подход: библиотеки и утилиты

Если вы не хотите писать свою реализацию глубокого копирования, можно использовать готовые решения из популярных библиотек:

  • Lodash — функция _.cloneDeep(value). Работает с любыми типами, включая функции, даты, Map, Set и циклические ссылки. Пример:
import _ from 'lodash';
let original = [{a: 1, b: {c: 2}}];
let deepCopy = _.cloneDeep(original);
  • jQuery — $.extend(true, {}, original) для глубокого копирования объектов.

Когда использовать: Если вы уже используете библиотеку в проекте, это самый надёжный и лаконичный вариант. Например, в React-проектах часто применяют Lodash для работы с состоянием.

Важно: Не подключайте целую библиотеку только ради одной функции — лучше используйте structuredClone или напишите свой клон. Но если библиотека уже есть, это экономит время и снижает риск ошибок.

Ещё один современный способ — Immer (библиотека для работы с иммутабельными данными). Она не копирует, а создаёт черновик, который затем финализируется. Это удобно для сложных стейтов, но требует изучения паттернов.