Что делать с объектом если не хочешь его изменять?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Что делать с объектом если не хочешь его изменять?
Если нужно работать с объектом, но не изменять исходные данные, есть несколько подходов: копирование, Immutability, Object.freeze() и функциональные методы.
Проблема: мутация исходного объекта
const user = { name: "John", age: 30 };
function updateUser(obj) {
obj.age = 31; // Изменяет исходный объект!
return obj;
}
const updated = updateUser(user);
console.log(user.age); // 31 - изменился!
console.log(updated === user); // true - это один и тот же объект
Способ 1: Поверхностное копирование (Shallow Copy)
Spread оператор - самый просто способ:
const user = { name: "John", age: 30 };
// Spread оператор
const updated = { ...user, age: 31 };
console.log(user); // { name: "John", age: 30 } - не изменился
console.log(updated); // { name: "John", age: 31 }
console.log(user === updated); // false - разные объекты
Object.assign():
const user = { name: "John", age: 30 };
const updated = Object.assign({}, user, { age: 31 });
console.log(user); // Оригинал не изменился
console.log(updated); // { name: "John", age: 31 }
Проблема поверхностного копирования:
const user = {
name: "John",
address: { city: "New York" } // вложенный объект
};
const updated = { ...user, address: { ...user.address, city: "Boston" } };
console.log(user.address.city); // "New York" - OK
console.log(updated.address.city); // "Boston" - OK
// Но если забыть про вложенные объекты:
const wrong = { ...user };
wrong.address.city = "Boston";
console.log(user.address.city); // "Boston" - изменился! Вложенный объект всё ещё общий
Способ 2: Глубокое копирование (Deep Copy)
JSON методом - для простых объектов:
const user = {
name: "John",
address: { city: "New York" }
};
const deepCopy = JSON.parse(JSON.stringify(user));
deepCopy.address.city = "Boston";
console.log(user.address.city); // "New York" - не изменился
console.log(deepCopy.address.city); // "Boston"
Проблемы JSON метода:
- Не работает с функциями
- Не работает с Date, Map, Set
- Не работает с undefined
- Очень медленно для больших объектов
const user = {
name: "John",
createdAt: new Date(), // Будет строкой!
process: () => {}, // Потеряется!
preferences: undefined // Потеряется!
};
const copy = JSON.parse(JSON.stringify(user));
console.log(copy.createdAt); // "2024-01-01T00:00:00.000Z" - строка, не Date
console.log(copy.process); // undefined - функция потеряна
structuredClone() - современный способ:
const user = {
name: "John",
createdAt: new Date(),
address: { city: "New York" }
};
const deepCopy = structuredClone(user);
deepCopy.address.city = "Boston";
console.log(user.address.city); // "New York" - OK
console.log(deepCopy.createdAt instanceof Date); // true - Date сохранился
Способ 3: Object.freeze() - "замораживание"
Object.freeze() предотвращает изменение объекта:
const user = { name: "John", age: 30 };
Object.freeze(user);
user.name = "Jane"; // Ошибка в strict mode, в обычном режиме игнорируется
user.newField = "value"; // Также игнорируется
console.log(user); // { name: "John", age: 30 } - не изменился
Проблема: shallow freeze
const user = {
name: "John",
address: { city: "New York" }
};
Object.freeze(user);
user.name = "Jane"; // Нельзя изменить
user.address.city = "Boston"; // Можно! (вложенный объект не заморожен)
console.log(user.address.city); // "Boston" - изменился!
Глубокое замораживание:
function deepFreeze(obj) {
Object.freeze(obj);
Object.values(obj).forEach(value => {
if (typeof value === "object" && value !== null) {
deepFreeze(value);
}
});
return obj;
}
const user = Object.freeze({
name: "John",
address: { city: "New York" }
});
deepFreeze(user);
user.address.city = "Boston"; // Ошибка в strict mode
console.log(user.address.city); // "New York"
Способ 4: Immutable libraries
Immer.js - очень удобная библиотека:
import { produce } from "immer";
const user = { name: "John", age: 30 };
const updated = produce(user, draft => {
draft.age = 31; // Выглядит как мутация, но создаёт новый объект
});
console.log(user); // { name: "John", age: 30 } - не изменился
console.log(updated); // { name: "John", age: 31 }
console.log(user === updated); // false
Immer с вложенными объектами:
const state = {
user: {
name: "John",
address: { city: "New York" }
}
};
const newState = produce(state, draft => {
draft.user.address.city = "Boston"; // Просто мутируй!
});
console.log(state.user.address.city); // "New York"
console.log(newState.user.address.city); // "Boston"
Способ 5: Функциональные методы массивов
Для массивов используй методы, которые не мутируют оригинал:
const items = [1, 2, 3, 4, 5];
// Плохо: мутирует оригинал
const sorted = items.sort((a, b) => b - a); // items тоже отсортируется!
// Хорошо: не мутирует
const sorted = [...items].sort((a, b) => b - a); // items не изменится
// Другие безопасные методы
const doubled = items.map(x => x * 2); // новый массив
const evens = items.filter(x => x % 2 === 0); // новый массив
const sum = items.reduce((a, b) => a + b, 0); // новое значение
Опасные методы (мутируют):
const items = [1, 2, 3];
items.push(4); // мутирует
items.pop(); // мутирует
items.splice(0, 1); // мутирует
items.reverse(); // мутирует
items.sort(); // мутирует
items[0] = 99; // мутирует
Безопасные альтернативы:
const items = [1, 2, 3];
const withItem = [...items, 4]; // Вместо push
const withoutLast = items.slice(0, -1); // Вместо pop
const withoutFirst = items.slice(1); // Вместо splice
const reversed = [...items].reverse(); // Вместо reverse
const sorted = [...items].sort(); // Вместо sort
React практический пример
import { useState } from "react";
import { produce } from "immer";
function UserForm() {
const [user, setUser] = useState({ name: "John", age: 30 });
// Способ 1: Spread operator
const handleNameChange = (e) => {
setUser({ ...user, name: e.target.value });
};
// Способ 2: Immer
const handleAgeChange = (e) => {
setUser(
produce(user, draft => {
draft.age = parseInt(e.target.value);
})
);
};
// Способ 3: функция с новым объектом
const handleReset = () => {
setUser({ name: "John", age: 30 });
};
return (
<div>
<input value={user.name} onChange={handleNameChange} />
<input value={user.age} onChange={handleAgeChange} />
<button onClick={handleReset}>Reset</button>
</div>
);
}
Таблица методов
| Метод | Глубокое копирование | Производительность | Поддержка типов | Сложность |
|---|---|---|---|---|
| Spread { ...obj } | Нет | Отличная | Все | Низкая |
| JSON.parse/stringify | Да | Плохая | Не все | Низкая |
| structuredClone() | Да | Хорошая | Большинство | Низкая |
| Object.freeze() | Нет | Отличная | Все | Средняя |
| Immer | Да | Хорошая | Все | Средняя |
Вывод
Для простых случаев:
- Используй spread оператор ({ ...obj, field: newValue })
Для сложной логики:
- Используй Immer.js (produce функция)
Для гарантированной защиты:
- Используй Object.freeze() (но помни про shallow)
Для массивов:
- Используй безопасные методы (.map, .filter, .concat)
- Избегай .push, .pop, .splice, .sort (без копирования)
Ключевой принцип: Не мутируй данные, которые используются в других частях кода - это главный источник багов!