Avatar ·

Как объединить свойства двух объектов JavaScript? (Object.assign, spread и другие способы)

Всем привет! Я начинающий JS-разработчик, столкнулся с проблемой. У меня есть два объекта с настройками: ```javascript const defaultSettings = { theme: 'light', fontSize: 14, showSidebar: true }; const userSettings = { fontSize: 16, showNotifications: true }; ``` Мне нужно объединить их так, чтобы в итоговом объекте были все свойства из `defaultSettings`, но если в `userSettings` есть такие же ключи (например, `fontSize`), то их значения должны перезаписывать значения по умолчанию. То есть я хочу получить: ```javascript { theme: 'light', fontSize: 16, // перезаписано из userSettings showSidebar: true, showNotifications: true // добавлено из userSettings } ``` Я пробовал просто присваивать: `const merged = { ...defaultSettings, ...userSettings }` — это работает. Но слышал, что есть и другие способы: `Object.assign()`, какие-то библиотеки вроде Lodash. Какой способ считается правильным и безопасным в современном JavaScript? Есть ли подводные камни, если объекты вложенные (например, объект внутри объекта)? Заранее спасибо за помощь! <<>>

Лучший способ: оператор spread (расширение) — современный и нативный

Вы уже на верном пути. Оператор ... (spread) — это самый простой и читаемый способ объединить свойства двух объектов в современном JavaScript (ES2018+). Ваш код корректен:

const merged = { ...defaultSettings, ...userSettings };

Принцип работы: свойства из второго объекта (userSettings) перезаписывают одноимённые свойства из первого (defaultSettings). Свойства, которые есть только в одном объекте, просто копируются. Это называется "поверхностное (shallow) объединение".

Альтернативный нативный метод: Object.assign()

До появления spread-оператора использовали Object.assign():

const merged = Object.assign({}, defaultSettings, userSettings);

Первый аргумент — пустой целевой объект (чтобы не мутировать исходные). Последующие объекты добавляются слева направо. Результат идентичен spread-оператору, но код чуть многословнее.

Важное предостережение: вложенные объекты (глубокое объединение)

И spread, и Object.assign() делают только поверхностное копирование. Если у вас есть вложенные объекты, они будут перезаписаны целиком, а не объединены рекурсивно. Пример:

const obj1 = { 
  user: { name: 'Alice', age: 25 },
  role: 'admin'
};
const obj2 = { 
  user: { name: 'Bob' }  // age пропадёт!
};

const merged = { ...obj1, ...obj2 };
console.log(merged);
// { user: { name: 'Bob' }, role: 'admin' }
// age: 25 потеряно!

Для глубокого (deep) объединения вложенных объектов нативный JavaScript не предоставляет встроенного метода. Вот три решения:

  • Рекурсивная функция (напишите сами):
function deepMerge(target, source) {
  const output = { ...target };
  for (let key in source) {
    if (source.hasOwnProperty(key)) {
      if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
        output[key] = deepMerge(target[key] || {}, source[key]);
      } else {
        output[key] = source[key];
      }
    }
  }
  return output;
}

const deepMerged = deepMerge(obj1, obj2);
// { user: { name: 'Bob', age: 25 }, role: 'admin' }
  • Используйте библиотеку Lodash (метод _.merge()):
// Подключите lodash: npm install lodash
const _ = require('lodash');
const merged = _.merge({}, obj1, obj2);
// Работает рекурсивно, не мутирует исходные объекты
  • Структурированное клонирование (только для глубокого копирования, не объединения):
const copy = structuredClone(originalObject);
// Но это не объединяет, а копирует один объект

Рекомендации по выбору

  • Для плоских объектов (без вложенности) — используйте spread-оператор. Это современно, быстро и читаемо.
  • Для глубокого объединения — либо напишите свою рекурсивную функцию (если логика простая), либо используйте _.merge() из Lodash (если проект уже использует эту библиотеку).
  • Избегайте мутации исходных объектов — всегда создавайте новый объект (пустой {} как первый аргумент в Object.assign или пустой объект слева от spread).

Ваш пример с настройками — идеальный случай для spread-оператора. Он решает задачу в одну строку и не требует дополнительных зависимостей.

<<>>

Альтернативный подход: использование JSON для глубокого копирования (с осторожностью)

Если вам нужно быстрое глубокое объединение без внешних библиотек, можно использовать трюк с JSON. Однако этот метод не рекомендуется для production из-за ограничений:

function deepMergeJSON(target, source) {
  // Превращаем всё в строку и обратно, но теряем функции, undefined, Symbol, даты
  const merged = JSON.parse(JSON.stringify(target));
  const sourceCopy = JSON.parse(JSON.stringify(source));
  
  for (let key in sourceCopy) {
    if (typeof sourceCopy[key] === 'object' && !Array.isArray(sourceCopy[key])) {
      merged[key] = { ...merged[key], ...sourceCopy[key] };
    } else {
      merged[key] = sourceCopy[key];
    }
  }
  return merged;
}

const result = deepMergeJSON(obj1, obj2);
// Работает, но не рекурсивно для глубокой вложенности!

Недостатки:

  • Теряются значения undefined, функции, Symbol, Date (превращаются в строки).
  • Не работает с циклическими ссылками.
  • Медленнее нативных методов.
  • Не делает полное глубокое объединение — только один уровень вложенности.

Когда это может пригодиться: только для простых объектов, содержащих примитивы и вложенные объекты без специальных типов данных. В остальных случаях лучше использовать рекурсивную функцию или Lodash.

Также стоит упомянуть, что в будущем в JavaScript может появиться нативный метод Object.merge() (предложение находится на стадии рассмотрения), но пока это не стандарт.