Введение: почему слияние объектов — это боль каждого JavaScript-разработчика
Представьте: вы работаете над приложением, и вам нужно объединить данные из двух разных источников. Например, у вас есть базовая конфигурация пользователя из базы данных и настройки, пришедшие с фронтенда. Как их соединить без боли и страданий? Именно этот вопрос — «How can I merge properties of two JavaScript objects?» — собрал тысячи просмотров на Stack Overflow. И не зря: слияние объектов в JavaScript таит в себе множество подводных камней.
В этой статье мы разберём все популярные способы объединения объектов в JS: от классического Object.assign() до современного spread-оператора. Вы узнаете, как глубоко копировать вложенные структуры, избегать мутации исходных данных и выбирать правильный метод под свою задачу. Поехали!
Проблема: что значит «объединить свойства двух объектов»?
Когда мы говорим «merge objects JavaScript», мы подразумеваем создание нового объекта, который содержит все свойства из исходных объектов. Если встречаются одинаковые ключи — значение из второго объекта «перезаписывает» значение из первого.
Допустим, есть два объекта:
const user = {
name: 'Анна',
age: 28,
city: 'Москва'
};
const updates = {
age: 29,
job: 'Разработчик'
};
После слияния объектов JS мы хотим получить:
{
name: 'Анна',
age: 29, // обновлённое значение
city: 'Москва',
job: 'Разработчик' // новое свойство
}
Казалось бы, просто. Но дьявол кроется в деталях: вложенные объекты, массивы, мутация оригинала и производительность.
Метод 1: Object.assign() — старый добрый классик
Метод Object.assign() — это самый старый и проверенный способ объединения объектов в JavaScript. Он копирует собственные перечисляемые свойства из одного или нескольких исходных объектов в целевой объект.
Синтаксис
Object.assign(target, ...sources)
Важно: первый аргумент — это целевой объект, который будет изменён. Все последующие — источники свойств.
Пример слияния
const user = { name: 'Анна', age: 28 };
const updates = { age: 29, job: 'Разработчик' };
const merged = Object.assign({}, user, updates);
console.log(merged);
// { name: 'Анна', age: 29, job: 'Разработчик' }
Обратите внимание: мы передаём пустой объект {} как target. Это гарантирует, что исходные объекты не будут мутированы — клонирование объектов JS выполняется корректно.
Подводный камень: поверхностное копирование
Object.assign() выполняет только поверхностное копирование (shallow copy). Если у вас есть вложенные объекты, они будут скопированы по ссылке:
const obj1 = { data: { value: 10 } };
const obj2 = { info: 'test' };
const merged = Object.assign({}, obj1, obj2);
merged.data.value = 999;
console.log(obj1.data.value); // 999 — мутация исходного объекта!
Для глубокого слияния понадобятся другие подходы, о которых мы поговорим ниже.
Метод 2: Spread-оператор — элегантность ES6
С появлением ES6 spread-оператор JavaScript (троеточие ...) стал самым популярным способом объединения свойств объектов. Он лаконичен, читаем и не требует вызова метода.
Синтаксис
const merged = { ...obj1, ...obj2 };
Пример
const defaults = { theme: 'light', lang: 'ru', showSidebar: true };
const userPrefs = { theme: 'dark', fontSize: 16 };
const settings = { ...defaults, ...userPrefs };
console.log(settings);
// { theme: 'dark', lang: 'ru', showSidebar: true, fontSize: 16 }
Порядок имеет значение: свойства из второго объекта перезаписывают свойства из первого. Именно поэтому theme стал 'dark', а lang остался 'ru'.
Преимущества перед Object.assign()
- Более короткий и читаемый синтаксис
- Не нужно помнить про пустой target
- Легко комбинировать с другими литералами объекта
Однако spread-оператор тоже делает только поверхностное слияние объектов JS.
Метод 3: Глубокое слияние — когда вложенность неизбежна
Если ваши объекты содержат вложенные структуры (объекты внутри объектов), ни Object.assign(), ни spread-оператор не помогут. Для глубокого слияния объектов JavaScript нужно использовать рекурсивные функции или библиотеки.
Самописная функция глубокого слияния
function deepMerge(target, source) {
const result = { ...target };
for (let key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
// Рекурсивно сливаем вложенные объекты
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
// Просто копируем примитивы и массивы
result[key] = source[key];
}
}
}
return result;
}
const obj1 = {
user: { name: 'Иван', address: { city: 'СПб', zip: 190000 } },
items: [1, 2]
};
const obj2 = {
user: { age: 30, address: { street: 'Невский' } },
items: [3, 4]
};
const merged = deepMerge(obj1, obj2);
console.log(merged);
/*
{
user: {
name: 'Иван',
age: 30,
address: { city: 'СПб', zip: 190000, street: 'Невский' }
},
items: [3, 4] // массивы перезаписываются, а не мержатся
}
*/
Обратите внимание: массивы в этой реализации просто перезаписываются, а не объединяются. Для слияния массивов нужна отдельная логика.
Готовые библиотеки для глубокого слияния
- Lodash.merge — самый популярный вариант, рекурсивно сливает любые структуры
- deepmerge — лёгкая библиотека, только для слияния
- structuredClone — современный нативный метод (но не сливает, а клонирует)
// Пример с Lodash
import _ from 'lodash';
const merged = _.merge(obj1, obj2);
Метод 4: JSON-трюк — быстрый, но опасный
Иногда разработчики используют JSON.parse(JSON.stringify()) для клонирования и слия