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

Делал ли глубокую копию

1.2 Junior🔥 161 комментариев
#JavaScript Core

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

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

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

Глубокое копирование (Deep Copy) в работе

Да, регулярно используюглубокое копирование. Это критично при работе с данными и состоянием в JavaScript.

Когда это нужно

1. Иммутабельность в React/Redux

// Плохо — мутируем оригинальный объект
const handleUpdate = (userId, newName) => {
  const user = users[userId];
  user.name = newName; // Мутация! React не заметит изменения
};

// Хорошо — создаём новый объект (shallow copy)
const handleUpdate = (userId, newName) => {
  const updatedUsers = {
    ...users,
    [userId]: { ...users[userId], name: newName }
  };
  setUsers(updatedUsers);
};

2. Глубокие структуры (nested objects)

// Shallow copy не подходит для вложенных объектов
const user = {
  id: 1,
  name: 'John',
  profile: {
    bio: 'Developer',
    address: {
      city: 'NYC'
    }
  }
};

// Shallow copy
const copied1 = { ...user };
copied1.profile.address.city = 'LA'; // Меняет оригинал!

// Deep copy — нужен для вложенных структур
const copied2 = JSON.parse(JSON.stringify(user));
copied2.profile.address.city = 'LA'; // Оригинал не меняется

Способы глубокого копирования

1. JSON метод (просто, но с минусами)

const deepCopy = (obj) => JSON.parse(JSON.stringify(obj));

// Работает для простых данных
const user = { name: 'John', age: 30 };
const copy = deepCopy(user);

// ПРОБЛЕМЫ:
// 1. Теряет функции
const obj = {
  name: 'John',
  greet() { return 'Hello'; }
};
const copy = deepCopy(obj);
copy.greet(); // TypeError: copy.greet is not a function

// 2. Теряет undefined
const obj = { a: 1, b: undefined };
const copy = deepCopy(obj);
console.log(copy.b); // undefined не скопирован!

// 3. Теряет Date
const obj = { date: new Date('2025-01-01') };
const copy = deepCopy(obj);
console.log(typeof copy.date); // string (не Date!)

// 4. Теряет Map/Set/Symbol
const obj = { map: new Map() };
const copy = deepCopy(obj); // Map преобразуется в {}

2. Рекурсивный способ (надёжнее)

const deepCopy = (obj) => {
  // Примитивы
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }
  
  // Массивы
  if (Array.isArray(obj)) {
    return obj.map(item => deepCopy(item));
  }
  
  // Объекты
  const copied = {};
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      copied[key] = deepCopy(obj[key]);
    }
  }
  return copied;
};

const user = {
  name: 'John',
  profile: {
    bio: 'Developer',
    skills: ['JS', 'React', 'Node']
  }
};

const copy = deepCopy(user);
copy.profile.skills[0] = 'TypeScript'; // Оригинал не меняется

3. Structured Clone API (современный стандарт)

// Поддержка: Chrome 98+, Firefox 98+, Safari 15.4+, Node 17+
const copy = structuredClone(user);

// Работает с Date, Map, Set, Blob, ArrayBuffer
const obj = {
  date: new Date(),
  map: new Map([['a', 1]]),
  set: new Set([1, 2, 3])
};
const copy = structuredClone(obj);
console.log(copy.date instanceof Date); // true!

// НО не работает с функциями
const obj = { greet: () => 'Hello' };
const copy = structuredClone(obj); // Error: not cloneable

4. Lodash (production-ready)

import _ from 'lodash';

const copy = _.cloneDeep(user);

// Работает с функциями, обычными объектами, массивами
// Поддерживает циклические ссылки
const circular = { a: 1 };
circular.self = circular;
const copy = _.cloneDeep(circular); // Работает!

В практике React

Обновление nested state:

function UserEditor() {
  const [user, setUser] = useState({
    name: 'John',
    profile: { bio: 'Developer' }
  });
  
  // Неправильно
  const handleBioChange = (newBio) => {
    user.profile.bio = newBio; // Мутация!
    setUser(user); // React не заметит изменения
  };
  
  // Правильно
  const handleBioChange = (newBio) => {
    setUser(prev => ({
      ...prev,
      profile: { ...prev.profile, bio: newBio }
    }));
  };
  
  // Или с глубокой копией
  const handleBioChange = (newBio) => {
    const updated = structuredClone(user);
    updated.profile.bio = newBio;
    setUser(updated);
  };
  
  return (
    <input
      value={user.profile.bio}
      onChange={(e) => handleBioChange(e.target.value)}
    />
  );
}

С Immer (лучше для сложных обновлений):

import { useImmer } from 'use-immer';

function UserEditor() {
  const [user, updateUser] = useImmer({
    name: 'John',
    profile: { bio: 'Developer' }
  });
  
  // Immer автоматически создаёт deep copy
  const handleBioChange = (newBio) => {
    updateUser(draft => {
      draft.profile.bio = newBio; // Выглядит как мутация
    });
  };
  
  return (
    <input
      value={user.profile.bio}
      onChange={(e) => handleBioChange(e.target.value)}
    />
  );
}

С Redux

// Bad pattern
const initialState = {
  users: [{ id: 1, name: 'John', profile: { bio: 'Dev' } }]
};

const reducer = (state, action) => {
  if (action.type === 'UPDATE_USER') {
    state.users[0].profile.bio = action.payload; // Мутация!
    return state;
  }
};

// Good pattern
const reducer = (state, action) => {
  if (action.type === 'UPDATE_USER') {
    return {
      ...state,
      users: state.users.map(u =>
        u.id === action.userId
          ? {
              ...u,
              profile: { ...u.profile, bio: action.payload }
            }
          : u
      )
    };
  }
};

// Best pattern (с Immer)
import produce from 'immer';

const reducer = produce((draft, action) => {
  if (action.type === 'UPDATE_USER') {
    const user = draft.users.find(u => u.id === action.userId);
    if (user) user.profile.bio = action.payload;
  }
});

Производительность

// Deep copy большого объекта — дорого!
const bigData = { /* 10000 объектов */ };

// Плохо
const copy = structuredClone(bigData); // Медленно

// Хорошо
const copy = { ...bigData }; // Быстро (shallow)

// Лучше
const copy = produce(bigData, draft => {
  // Изменяем только нужные поля
}); // Копирует только изменённые ветки

Когда НЕ нужна глубокая копия

// Передаём данные в дочерний компонент
const UserCard = ({ user }) => {
  // user — read-only, не мутируем
  return <div>{user.name}</div>;
};

// НЕ нужна копия здесь
<UserCard user={userData} /> // OK

// Нужна только если мутируем
const UserEditor = ({ user: initialUser }) => {
  const [user, setUser] = useState(initialUser);
  // Теперь user — наше состояние, можно мутировать
};

Мой выбор в production

// 1. Для простых объектов и массивов → spread operator
setUser({ ...user, name: newName });

// 2. Для вложенных структур → structuredClone (если поддержка браузеров OK)
const copy = structuredClone(user);

// 3. Для сложных обновлений → Immer
updateUser(draft => {
  draft.profile.bio = newBio;
});

// 4. Для полной поддержки браузеров → Lodash
import _ from 'lodash';
const copy = _.cloneDeep(user);

Главное правило

В React/Redux: используй иммутабельные обновления. Глубокая копия — инструмент для этого, но не все случаи требуют глубокой копии. Обычно хватает shallow copy через spread оператор.