← Назад к вопросам

Что делать с объектом если не хочешь его изменять?

2.2 Middle🔥 172 комментариев
#Архитектура и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что делать с объектом если не хочешь его изменять?

Если нужно работать с объектом, но не изменять исходные данные, есть несколько подходов: копирование, 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 (без копирования)

Ключевой принцип: Не мутируй данные, которые используются в других частях кода - это главный источник багов!