DeepClone: примеры (JAVASCRIPT)
deepClone(obj: Any): AnyФункция deepClone предназначена для создания полной независимой копии сложного объекта, включая все вложенные объекты, массивы и другие структуры данных. В отличие от поверхностного копирования, глубокое копирование гарантирует, что изменения в копии не затронут исходный объект.
Общее описание
В стандартной библиотеке JavaScript нет встроенной функции deepClone. Ее реализуют самостоятельно или используют сторонние библиотеки. Функция рекурсивно обходит все свойства исходного объекта, создавая новые экземвалеты для каждого найденного объекта или массива.
Параметры, которые часто реализуют в таких функциях:
- target - объект, массив или примитивное значение для копирования (обязательный).
- visited - внутренний параметр для отслеживания уже скопированных объектов, предотвращающий зацикливание при циклических ссылках.
- customizer - необязательная функция-обработчик, позволяющая кастомизировать процесс копирования для определенных типов данных.
Функция возвращает новую структуру данных, идентичную исходной, но не связанную с ней ссылками.
Примеры использования
Простая реализация и ее применение:
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
const clone = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
clone[key] = deepClone(obj[key]);
}
}
return clone;
}
// Пример 1: Копирование объекта
const original = { a: 1, b: { c: 2 } };
const cloned = deepClone(original);
cloned.b.c = 99;
console.log(original.b.c); // Осталось 2
console.log(cloned.b.c); // Изменено на 992 99
Пример с массивом:
const originalArray = [1, [2, 3], { x: 4 }];
const clonedArray = deepClone(originalArray);
clonedArray[1][0] = 999;
console.log(originalArray[1][0]); // Осталось 2
console.log(clonedArray[1][0]); // Изменено на 9992 999
Альтернативные подходы в JavaScript
JSON.parse(JSON.stringify(obj)) - простой метод для объектов, содержащих только сериализуемые данные (без функций, Symbol, undefined и циклических ссылок).
const obj = { a: 1, b: { c: 2 } };
const clone = JSON.parse(JSON.stringify(obj));
console.log(clone);{ a: 1, b: { c: 2 } }Object.assign() и spread оператор - создают только поверхностную копию.
const obj = { a: 1, b: { c: 2 } };
const shallowClone = { ...obj };
shallowClone.b.c = 99;
console.log(obj.b.c); // Изменилось на 9999
structuredClone() - современная нативная функция для глубокого копирования, поддерживаемая в новых средах.
const obj = { a: 1, b: { c: 2 } };
const clone = structuredClone(obj);
clone.b.c = 99;
console.log(obj.b.c); // Осталось 22
Реализации в других языках
Python: модуль copy с функциями copy() и deepcopy().
import copy
original = {'a': 1, 'b': {'c': 2}}
cloned = copy.deepcopy(original)
cloned['b']['c'] = 99
print(original['b']['c']) # Осталось 2
print(cloned['b']['c']) # Изменено на 992 99
PHP: используется ключевое слово clone для объектов, но для глубокого копирования нужна реализация метода __clone().
class MyClass {
public $prop;
public function __clone() {
$this->prop = clone $this->prop;
}
}
$obj1 = new MyClass();
$obj1->prop = new stdClass();
$obj2 = clone $obj1; // Поверхностное копирование
// Для глубокого нужно __clone()C++: глубокое копирование обычно реализуется через конструктор копирования и перегрузку оператора присваивания.
Типичные ошибки
Циклические ссылки вызывают бесконечную рекурсию в простых реализациях.
const obj = { a: 1 };
obj.self = obj; // Циклическая ссылка
function naiveDeepClone(obj) {
return JSON.parse(JSON.stringify(obj)); // Ошибка!
}
// naiveDeepClone(obj); // TypeError: Converting circular structure to JSONПотеря специальных типов при использовании JSON методов.
const obj = { date: new Date(), func: () => {} };
const clone = JSON.parse(JSON.stringify(obj));
console.log(typeof clone.date); // string
console.log(typeof clone.func); // undefinedstring undefined
Некорректная обработка прототипов.
function Person(name) { this.name = name; }
Person.prototype.greet = function() { return `Hello, ${this.name}`; };
const john = new Person('John');
const clone = deepClone(john); // В простой реализации
console.log(clone.greet); // undefined, метод прототипа потерянИзменения и современные подходы
Долгое время JavaScript не имел нативной функции глубокого копирования. В 2021 году появилась функция structuredClone(), ставшая стандартом.
Ключевые отличия structuredClone() от пользовательских реализаций:
- Поддержка циклических ссылок.
- Копирование широкого набора типов: Array, Object, Date, Set, Map, RegExp, ArrayBuffer и других.
- Невозможность копировать функции, DOM-узлы и прототипы.
- Доступность в глобальной области видимости.
// Современный подход
const original = { a: 1, b: { c: 2 } };
const clone = structuredClone(original);
console.log(clone);{ a: 1, b: { c: 2 } }Расширенные примеры
Реализация с обработкой циклических ссылок и различных типов данных:
function advancedDeepClone(target, visited = new WeakMap()) {
// Примитивы и null
if (target === null || typeof target !== 'object') {
return target;
}
// Обработка циклических ссылок
if (visited.has(target)) {
return visited.get(target);
}
// Специальные типы
if (target instanceof Date) return new Date(target);
if (target instanceof RegExp) return new RegExp(target);
if (target instanceof Map) {
const clone = new Map();
visited.set(target, clone);
target.forEach((value, key) => {
clone.set(key, advancedDeepClone(value, visited));
});
return clone;
}
if (target instanceof Set) {
const clone = new Set();
visited.set(target, clone);
target.forEach(value => {
clone.add(advancedDeepClone(value, visited));
});
return clone;
}
// Объекты и массивы
const clone = Array.isArray(target) ? [] : {};
visited.set(target, clone);
for (let key in target) {
if (target.hasOwnProperty(key)) {
clone[key] = advancedDeepClone(target[key], visited);
}
}
// Копирование символьных свойств
const symbolKeys = Object.getOwnPropertySymbols(target);
for (let symKey of symbolKeys) {
clone[symKey] = advancedDeepClone(target[symKey], visited);
}
return clone;
}
// Пример с циклической ссылкой и Map
const original = { name: "Test" };
original.self = original;
original.map = new Map([['key', { value: 42 }]]);
const cloned = advancedDeepClone(original);
cloned.map.get('key').value = 100;
console.log(original.map.get('key').value); // 42
console.log(cloned.self === cloned); // true, циклическая ссылка сохранена42 true
Пример с кастомизацией через функцию:
function deepCloneWithCustomizer(target, customizer) {
if (customizer && typeof customizer === 'function') {
const result = customizer(target);
if (result !== undefined) return result;
}
if (target === null || typeof target !== 'object') return target;
const clone = Array.isArray(target) ? [] : {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
clone[key] = deepCloneWithCustomizer(target[key], customizer);
}
}
return clone;
}
// Кастомизация для определенного типа
const obj = { date: new Date('2023-01-01'), data: { x: 1 } };
const customizer = (value) => {
if (value instanceof Date) {
return `Custom: ${value.toISOString().slice(0, 10)}`;
}
};
const result = deepCloneWithCustomizer(obj, customizer);
console.log(result.date); // Строка вместо Date объектаCustom: 2023-01-01