Ответ
Пример — функция глубокого слияния (deep merge) объектов с обработкой специальных типов данных и защитой от циклических ссылок.
/**
* Рекурсивно глубоко объединяет два и более объектов.
* @param {Object} target - Целевой объект, в который производится слияние.
* @param {...Object} sources - Исходные объекты.
* @returns {Object} - Новый, глубоко объединенный объект.
*/
function deepMerge(target, ...sources) {
// Если target не объект или null, возвращаем его же (или новый объект)
if (typeof target !== 'object' || target === null) {
target = {};
}
for (const source of sources) {
// Пропускаем undefined или null источники
if (source == null) continue;
for (const key in source) {
// Обрабатываем только собственные свойства источника
if (!source.hasOwnProperty(key)) continue;
const targetValue = target[key];
const sourceValue = source[key];
// Рекурсивное слияние, если оба значения — объекты и не являются массивами
if (isObject(targetValue) && isObject(sourceValue) && !Array.isArray(sourceValue)) {
// Защита от циклических ссылок: не мерджим объект с самим собой
if (sourceValue !== targetValue) {
target[key] = deepMerge({}, targetValue, sourceValue); // Создаем новый объект для слияния
}
} else {
// Копирование примитивов, массивов, функций и других объектов
target[key] = cloneValue(sourceValue);
}
}
}
return target;
}
/** Проверяет, является ли значение объектом (но не null). */
function isObject(value) {
return typeof value === 'object' && value !== null;
}
/** Глубоко клонирует значение с учетом специальных типов. */
function cloneValue(value) {
if (Array.isArray(value)) {
// Клонируем массив, рекурсивно клонируя его элементы
return value.map(item => cloneValue(item));
}
if (value instanceof Date) {
// Создаем новый объект Date
return new Date(value.getTime());
}
if (value instanceof RegExp) {
// Клонируем регулярное выражение
return new RegExp(value.source, value.flags);
}
if (isObject(value)) {
// Для простых объектов — рекурсивный клон
return deepMerge({}, value);
}
// Примитивы (числа, строки, boolean, undefined, функции) возвращаются как есть
return value;
}
// Пример использования
const defaults = {
api: {
url: 'https://api.example.com',
timeout: 5000,
headers: { 'User-Agent': 'App' }
},
features: ['search', 'filter'],
created: new Date('2023-01-01')
};
const userConfig = {
api: {
timeout: 10000,
headers: { 'Authorization': 'Bearer token123' }
},
features: ['export'], // Массив перезаписывается, а не мерджится!
retryPolicy: { attempts: 3 }
};
const mergedConfig = deepMerge({}, defaults, userConfig);
console.log(mergedConfig);
/*
{
api: {
url: 'https://api.example.com', // из defaults
timeout: 10000, // перезаписано из userConfig
headers: { // глубоко объединено
'User-Agent': 'App',
'Authorization': 'Bearer token123'
}
},
features: ['export'], // массив перезаписан
created: Date('2023-01-01T00:00:00.000Z'), // склонированная дата
retryPolicy: { attempts: 3 } // добавлено из userConfig
}
*/
Что делает код сложным:
- Рекурсия для обработки вложенных структур.
- Различение типов: объекты, массивы, даты, регулярки обрабатываются по-разному.
- Защита от циклических ссылок: проверка
if (sourceValue !== targetValue). - Иммутабельность: функция не мутирует исходные объекты, создавая новые (
deepMerge({}, ...)). - Обработка собственных свойств:
hasOwnPropertyигнорирует унаследованные свойства из цепочки прототипов.
Альтернатива в современном JS: использование structuredClone() для глубокого клонирования и библиотек вроде Lodash (_.merge()).