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

В чем разница между spread и Object.assign()?

2.0 Middle🔥 201 комментариев
#JavaScript Core

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

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

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

Разница между Spread оператором и Object.assign()

Оба способа используются для копирования и объединения объектов в JavaScript, но они имеют важные различия в поведении, синтаксисе и производительности. Разберемся подробно.

1. Синтаксис

Spread оператор (...):

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3 };

// Новый объект с объединением
const merged = { ...obj1, ...obj2 };
console.log(merged);  // { a: 1, b: 2, c: 3 }

Object.assign():

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3 };

// Объединяет в первый аргумент (мутирует его!)
const merged = Object.assign({}, obj1, obj2);
console.log(merged);  // { a: 1, b: 2, c: 3 }

2. Мутирование vs создание нового объекта

Это ГЛАВНОЕ различие:

Spread оператор — всегда создает новый объект:

const original = { a: 1 };
const copy = { ...original };

original === copy;  // false — разные объекты
copy.a = 100;
console.log(original.a);  // 1 — оригинал не изменился

Object.assign() — МУТИРУЕТ первый аргумент:

const original = { a: 1 };
const copy = Object.assign(original, { b: 2 });

original === copy;  // true — ОДИН И ТОТ ЖЕ объект!
original.b;  // 2 — оригинал изменился!

Поэтому нужно передавать пустой объект в качестве первого аргумента:

const original = { a: 1 };
const copy = Object.assign({}, original, { b: 2 });

original === copy;  // false — разные объекты
original.b;  // undefined — оригинал не изменился

3. Глубокое копирование (Shallow copy)

ОБА способа создают shallow copy (поверхностное копирование), а не глубокое:

const original = {
  a: 1,
  nested: { b: 2 }
};

// Spread
const copy1 = { ...original };

// Object.assign
const copy2 = Object.assign({}, original);

// Оба поведения одинаковые:
copy1.nested === original.nested;  // true — один и тот же объект!
copy2.nested === original.nested;  // true — один и тот же объект!

// Изменение вложенного объекта влияет на оригинал
copy1.nested.b = 999;
console.log(original.nested.b);  // 999 — оригинал изменился!

Для глубокого копирования нужна рекурсия или библиотека:

// Способ 1: JSON (работает для простых объектов)
const deepCopy = JSON.parse(JSON.stringify(original));

// Способ 2: structuredClone (современный способ)
const deepCopy = structuredClone(original);

// Способ 3: Lodash
const deepCopy = _.cloneDeep(original);

// Способ 4: Рекурсивная функция
function deepClone(obj) {
  if (typeof obj !== 'object' || obj === null) return obj;
  if (obj instanceof Array) return obj.map(deepClone);
  return Object.fromEntries(
    Object.entries(obj).map(([k, v]) => [k, deepClone(v)])
  );
}

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

Spread оператор обычно быстрее:

// Бенчмарк
const obj = { a: 1, b: 2, c: 3, d: 4, e: 5 };

// Spread
console.time('spread');
for (let i = 0; i < 1000000; i++) {
  const copy = { ...obj };
}
console.timeEnd('spread');  // ~40ms

// Object.assign
console.time('assign');
for (let i = 0; i < 1000000; i++) {
  const copy = Object.assign({}, obj);
}
console.timeEnd('assign');  // ~50ms

// Разница 20%, но на практике незначима

5. Порядок слияния

Оба оператора переписывают значения, но порядок важен:

const obj1 = { a: 1, b: 2 };
const obj2 = { b: 20, c: 3 };
const obj3 = { c: 30, d: 4 };

// Spread — последний побеждает
const merged1 = { ...obj1, ...obj2, ...obj3 };
console.log(merged1);  // { a: 1, b: 20, c: 30, d: 4 }

// Object.assign — последний побеждает
const merged2 = Object.assign({}, obj1, obj2, obj3);
console.log(merged2);  // { a: 1, b: 20, c: 30, d: 4 }

// Одинаково!

6. Работа с массивами и итерируемыми объектами

Spread работает с любыми итерируемыми:

// С объектами
const obj = { a: 1 };
const spread = { ...obj, b: 2 };

// С массивами
const arr = [1, 2, 3];
const spreadArr = [...arr, 4, 5];

// С Set
const set = new Set([1, 2, 3]);
const spreadSet = [...set];  // [1, 2, 3]

// С Map (трансформируется в пары ключ-значение)
const map = new Map([['a', 1], ['b', 2]]);
const spreadMap = [...map];  // [['a', 1], ['b', 2]]

Object.assign работает только с объектами:

// С объектами
const obj = { a: 1 };
const assigned = Object.assign({}, obj, { b: 2 });

// С массивами НЕ работает правильно
const arr = [1, 2, 3];
const assignedArr = Object.assign([], arr, { 0: 10 });
// Результат: { 0: 10, 1: 2, 2: 3 } — лучше не делать

// С другими итерируемыми не работает
const set = new Set([1, 2, 3]);
const assignedSet = Object.assign({}, set);  // {} — пусто

7. Обработка дескрипторов свойств

Различие в копировании дескрипторов (advanced):

const original = {};
Object.defineProperty(original, 'a', {
  value: 1,
  writable: false,     // только для чтения
  configurable: false
});

// Spread создает обычное свойство
const spread = { ...original };
Object.getOwnPropertyDescriptor(spread, 'a');
// { value: 1, writable: true, configurable: true }

// Object.assign скопирует дескриптор
const assigned = Object.assign({}, original);
Object.getOwnPropertyDescriptor(assigned, 'a');
// { value: 1, writable: false, configurable: false }

8. Символы и скрытые свойства

Spread НЕ копирует Symbol свойства:

const sym = Symbol('key');
const obj = { a: 1, [sym]: 'hidden' };

const spread = { ...obj };
console.log(spread[sym]);  // undefined

Object.assign копирует Symbol свойства:

const assigned = Object.assign({}, obj);
console.log(assigned[sym]);  // 'hidden'

9. Практические примеры

Обновление состояния в React (ПРАВИЛЬНО):

// Spread (рекомендуется)
const newUser = { ...user, name: 'Alice' };
setUser(newUser);

// Object.assign (тоже работает, но менее идиоматичный)
const newUser = Object.assign({}, user, { name: 'Alice' });
setUser(newUser);

Аргументы функции:

function request(url, options = {}) {
  // Дефолты с spread
  const finalOptions = {
    method: 'GET',
    timeout: 5000,
    ...options  // переписывает дефолты
  };
}

request('/api/users', { method: 'POST' });
// finalOptions = { method: 'POST', timeout: 5000 }

Объединение конфигов:

const defaultConfig = { debug: false, port: 3000 };
const envConfig = { port: process.env.PORT };
const userConfig = { debug: true };

// Spread (чистче)
const finalConfig = {
  ...defaultConfig,
  ...envConfig,
  ...userConfig
};

// Object.assign (более verbose)
const finalConfig = Object.assign(
  {},
  defaultConfig,
  envConfig,
  userConfig
);

Таблица сравнения

┌──────────────────────┬────────────┬──────────────────┐
│ Характеристика       │ Spread (...) │ Object.assign()  │
├──────────────────────┼────────────┼──────────────────┤
│ Синтаксис            │ Чистый     │ Многословный     │
│ Мутирует оригинал    │ НЕТ        │ ДА (первый арг)  │
│ Глубокое копирование │ НЕТ        │ НЕТ              │
│ Производительность   │ Быстрее    │ Медленнее         │
│ Работает с символами │ НЕТ        │ ДА               │
│ Копирует дескрипторы │ НЕТ        │ ДА               │
│ Работает с массивами │ ДА         │ НЕ рекомендуется │
│ Поддержка браузеров  │ ES2018+    │ ES2015+          │
└──────────────────────┴────────────┴──────────────────┘

Итог

Spread оператор ({ ...obj }) обычно предпочтительнее: более чистый синтаксис, не мутирует оригинал, быстрее и современнее. Object.assign() полезен, если нужно копировать Symbol свойства или дескрипторы. Для большинства случаев в React и современном коде используйте spread. Помните, что оба способа создают только shallow copy — для глубокого копирования используйте structuredClone() или JSON.parse(JSON.stringify()).

В чем разница между spread и Object.assign()? | PrepBro