Какой есть нюанс при копировании объекта через Object.assign?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Ключевой нюанс Object.assign: поверхностное копирование (shallow copy)
Основной и самый критичный нюанс Object.assign() заключается в том, что он выполняет поверхностное копирование, а не глубокое (deep copy). Это означает, что если объект содержит вложенные объекты или массивы, они копируются по ссылке, а не по значению.
Техническая демонстрация проблемы
const original = {
name: 'Иван',
address: {
city: 'Москва',
street: 'Тверская'
},
hobbies: ['чтение', 'спорт']
};
// Поверхностное копирование через Object.assign
const copy = Object.assign({}, original);
// Изменяем вложенный объект в копии
copy.address.city = 'Санкт-Петербург';
copy.hobbies.push('программирование');
console.log(original.address.city); // 'Санкт-Петербург' - ИЗМЕНИЛОСЬ!
console.log(original.hobbies); // ['чтение', 'спорт', 'программирование'] - ИЗМЕНИЛОСЬ!
Другие важные нюансы
1. Копирование только перечисляемых собственных свойств
Object.assign() копирует только те свойства, которые являются:
- Собственными (не унаследованными от прототипа)
- Перечисляемыми (enumerable: true)
const proto = { inheritedProp: 'из прототипа' };
const obj = Object.create(proto);
obj.ownEnumerable = 'собственное перечисляемое';
Object.defineProperty(obj, 'ownNonEnumerable', {
value: 'собственное неперечисляемое',
enumerable: false
});
const copy = Object.assign({}, obj);
console.log(copy.inheritedProp); // undefined
console.log(copy.ownEnumerable); // 'собственное перечисляемое'
console.log(copy.ownNonEnumerable); // undefined
2. Перезапись свойств при совпадении имён
Свойства копируются последовательно, и при совпадении имён более поздние значения перезаписывают предыдущие:
const target = { x: 1, y: 2 };
const source1 = { y: 10, z: 20 };
const source2 = { y: 100, z: 200 };
const result = Object.assign(target, source1, source2);
console.log(result); // { x: 1, y: 100, z: 200 }
console.log(target); // { x: 1, y: 100, z: 200 } - исходный target изменился!
3. Изменение целевого объекта
Первый аргумент (target) мутируется и возвращается. Это может быть неочевидно:
const original = { a: 1 };
const source = { b: 2 };
// Возвращаемое значение - тот же объект, что и original
const returned = Object.assign(original, source);
console.log(original === returned); // true
console.log(original); // { a: 1, b: 2 } - original ИЗМЕНЁН!
4. Не копируются геттеры и сеттеры
Object.assign() копирует значения свойств, а не дескрипторы свойств:
const source = {
get currentYear() {
return new Date().getFullYear();
},
regularProp: 'обычное свойство'
};
const copy = Object.assign({}, source);
console.log(typeof copy.currentYear); // 'number' (значение), а не 'function'
console.log(copy.currentYear); // 2024 (или текущий год), но это просто число
Решения для глубокого копирования
1. Рекурсивная реализация
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 cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
2. Использование JSON методов (с ограничениями)
const deepCopy = JSON.parse(JSON.stringify(original));
// Не копирует функции, undefined, символы, циклические ссылки
3. Современные подходы
// ES2022: structuredClone() для глубокого копирования
const deepCopy = structuredClone(original);
// Использование спред-оператора для поверхностного копирования
const shallowCopy = { ...original }; // Аналогично Object.assign({}, original)
Когда использовать Object.assign?
Подходящие случаи:
- Копирование плоских объектов без вложенностей
- Слияние конфигурационных объектов
- Создание неглубоких иммутабельных обновлений в Redux-редьюсерах
Неподходящие случаи:
- Работа с глубоко вложенными структурами данных
- Копирование объектов с методами (функциями)
- Работа с специальными типами (Date, Map, Set, RegExp)
Вывод
Object.assign() — эффективный инструмент для поверхностного копирования, но критически важно понимать его ограничения. В современных проектах часто предпочитают использовать спред-оператор (...) для поверхностного копирования из-за его более чистого синтаксиса, а для глубокого копирования — structuredClone() или специализированные библиотеки типа Lodash (_.cloneDeep()). Понимание этого нюанса помогает избежать тонких багов, связанных с непреднамеренным мутированием исходных данных.