Avatar ·

Самый эффективный способ глубокого клонирования объекта в JavaScript

Всем привет! Я новичок в JavaScript и столкнулся с проблемой при копировании объектов. Мне нужно создать полную независимую копию объекта, чтобы изменения в копии не влияли на оригинал. Но обычное присваивание не работает — копия просто ссылается на тот же объект. Вот пример моего кода: ```javascript const original = { name: "Алиса", age: 30, address: { city: "Москва", zip: 101000 }, hobbies: ["чтение", "плавание"] }; // Пытаюсь скопировать const copy = original; copy.name = "Боб"; copy.address.city = "Санкт-Петербург"; console.log(original.name); // "Боб" — ой, изменилось! console.log(original.address.city); // "Санкт-Петербург" — тоже изменилось! ``` Я пробовал использовать `Object.assign()` и оператор spread `{...obj}`, но они создают только поверхностную копию. Вложенные объекты и массивы всё равно остаются связанными. Например: ```javascript const copy2 = {...original}; copy2.address.city = "Казань"; console.log(original.address.city); // "Казань" — опять изменилось! ``` Я слышал про `JSON.parse(JSON.stringify(obj))`, но читал, что он не работает с функциями, undefined, Symbol и специальными объектами вроде Date. Есть ли более надёжный и производительный способ? Что считается лучшей практикой в 2024 году для глубокого клонирования сложных объектов? Заранее спасибо за помощь! <<>>

Лучший способ: встроенный метод structuredClone()

Начиная с Node.js 17 и современных браузеров (Chrome 98+, Firefox 94+, Safari 15.4+), появился встроенный метод structuredClone(). Это официально рекомендуемый способ глубокого клонирования, который решает большинство проблем старых подходов.

const original = {
  name: "Алиса",
  age: 30,
  address: { city: "Москва", zip: 101000 },
  hobbies: ["чтение", "плавание"],
  birthDate: new Date("1994-05-20"),
  nested: { deep: { value: 42 } }
};

const clone = structuredClone(original);

// Проверяем независимость
clone.address.city = "Санкт-Петербург";
clone.hobbies.push("теннис");
clone.birthDate.setFullYear(2000);

console.log(original.address.city); // "Москва" — не изменилось
console.log(original.hobbies); // ["чтение", "плавание"] — не изменилось
console.log(original.birthDate); // 1994-05-20 — не изменилось

Что structuredClone() умеет?

  • Глубокое клонирование всех вложенных объектов и массивов
  • Поддержка Date, Map, Set, RegExp, ArrayBuffer и других типов
  • Обработка циклических ссылок — не вызывает бесконечную рекурсию
  • Высокая производительность — реализован на уровне движка JavaScript

Что structuredClone() НЕ клонирует?

  • Функции — вызовет ошибку "DataCloneError"
  • Symbol — игнорируются
  • WeakMap, WeakSet — не поддерживаются
  • Прототипы и геттеры/сеттеры — копируются только значения

Когда structuredClone() недоступен: альтернативы

Если нужно поддерживать старые браузеры или Node.js < 17, используйте проверенные библиотеки:

// Вариант 1: Lodash (самый популярный)
import _ from 'lodash';
const lodashClone = _.cloneDeep(original);

// Вариант 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));
  
  const clonedObj = {};
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clonedObj[key] = deepClone(obj[key]);
    }
  }
  return clonedObj;
}

Почему JSON.parse/JSON.stringify — плохой выбор?

const obj = {
  name: "Тест",
  date: new Date(),
  func: () => console.log("привет"),
  undef: undefined,
  nan: NaN
};

const badClone = JSON.parse(JSON.stringify(obj));
console.log(badClone.date); // Строка, а не Date!
console.log(badClone.func); // undefined — функция потеряна
console.log(badClone.undef); // undefined — ключ пропал
console.log(badClone.nan); // null — NaN превратился в null

Рекомендация

В 2024 году для 95% случаев используйте structuredClone(). Если нужна поддержка функций — перепроектируйте архитектуру (функции не должны быть частью данных). Для легаси-проектов установите lodash через npm. И никогда не используйте JSON.parse/stringify для production-кода — это источник трудноуловимых багов.

<<>>

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

Если вы работаете в проекте, где уже используется Lodash (одна из самых популярных утилитарных библиотек), или вам нужно клонировать объекты с функциями и символами — _.cloneDeep() будет лучшим выбором. В отличие от structuredClone(), Lodash обрабатывает функции (копирует ссылки на них) и символы.

import _ from 'lodash';

const complexObj = {
  method: function() { return 42; },
  [Symbol('id')]: 123,
  nested: { data: [1, 2, { x: 10 }] }
};

const cloned = _.cloneDeep(complexObj);
console.log(cloned.method()); // 42 — функция работает
console.log(cloned.nested.data[2].x); // 10 — глубоко скопировано

Когда использовать Lodash вместо structuredClone?

  • Нужна поддержка старых браузеров (IE11, Safari < 15.4)
  • Объекты содержат функции или символы
  • Вы уже используете Lodash в проекте (не стоит добавлять библиотеку только ради cloneDeep)

Но помните: _.cloneDeep() медленнее нативного structuredClone() примерно в 2-4 раза для больших объектов. Для большинства веб-приложений эта разница незаметна, но если вы клонируете гигабайтные структуры данных — предпочтите нативный метод.