И Object.assign, и распространение объекта делают только поверхностное слияние.
Пример проблемы:
// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }
Выход - то, что вы ожидаете. Однако, если я попробую это:
// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }
Вместо того
{ a: { a: 1, b: 1 } }
ты получаешь
{ a: { b: 1 } }
x полностью перезаписан, потому что синтаксис распространения распространяется только на один уровень. Это то же самое с Object.assign()
.
Есть ли способ сделать это?
Ответы:
Нет.
источник
Я знаю, что это немного старая проблема, но самое простое решение в ES2015 / ES6, которое я мог придумать, было на самом деле довольно простым, с использованием Object.assign (),
Надеюсь, это поможет:
Пример использования:
Вы найдете неизменную версию этого в ответе ниже.
Обратите внимание, что это приведет к бесконечной рекурсии по циклическим ссылкам. Здесь есть несколько отличных ответов о том, как обнаружить циклические ссылки, если вы думаете, что столкнетесь с этой проблемой.
источник
item !== null
не должен быть нужен внутриisObject
, потому чтоitem
уже проверен на правдивость в начале условияObject.assign(target, { [key]: {} })
если это может быть простоtarget[key] = {}
?target[key] = source[key]
вместоObject.assign(target, { [key]: source[key] });
target
. Например, вmergeDeep({a: 3}, {a: {b: 4}})
результате получится дополненныйNumber
объект, который явно нежелателен. Кроме того,isObject
не принимает массивы, но принимает любой другой собственный тип объекта, такой какDate
, который не должен быть глубоко скопирован.Вы можете использовать Lodash Merge :
источник
{ 'a': [{ 'b': 2 }, { 'c': 3 }, { 'd': 4 }, { 'e': 5 }] }
?{ 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
правильный, потому что мы объединяем элементы массива. Элемент0
изobject.a
IS{b: 2}
, элемент0
изother.a
это{c: 3}
. Когда эти два слиты из-за того, что они имеют один и тот же индекс массива, результатом является{ 'b': 2, 'c': 3 }
элемент0
нового объекта.Проблема нетривиальна, когда речь идет о хост-объектах или объектах любого вида, более сложных, чем пакет значений.
Еще одна вещь, которую нужно иметь в виду: графы объектов, которые содержат циклы. Это обычно не сложно иметь дело - просто держите
Set
уже посещенные исходные объекты - но часто забывают.Вам, вероятно, следует написать функцию глубокого слияния, которая ожидает только примитивные значения и простые объекты - в большинстве случаев те типы, которые может обрабатывать алгоритм структурированного клона - в качестве источников слияния. Бросьте, если он сталкивается с чем-то, что он не может обработать или просто назначить по ссылке вместо глубокого слияния.
Другими словами, не существует единого алгоритма, подходящего для всех, вы должны либо свернуть свой собственный, либо искать библиотечный метод, который подходит для ваших вариантов использования.
источник
Вот неизменяемая (не изменяющая входные данные) версия ответа @ Salakar. Полезно, если вы занимаетесь функциональным программированием.
источник
key
качестве имени свойства, позднее будет делать «ключ» именем свойства. См. Es6-features.org/#ComputedPropertyNamesisObject
вам не нужно проверить&& item !== null
в конце, потому что начинается линия сitem &&
, нет?mergedDeep
выводе (я думаю). Например,const target = { a: 1 }; const source = { b: { c: 2 } }; const merged = mergeDeep(target, source);
merged.b.c; // 2
source.b.c = 3;
merged.b.c; // 3
это проблема? Он не мутирует входы, но любые будущие мутации на входах могут изменять выход, и наоборот, с мутациями для вывода изменяющихся входов. Что бы это ни стоило, ramdaR.merge()
имеет такое же поведение.Так как эта проблема все еще активна, вот другой подход:
источник
prev[key] = pVal.concat(...oVal);
наprev[key] = [...pVal, ...oVal].filter((element, index, array) => array.indexOf(element) === index);
reduce
- нет.Я знаю, что ответов уже много и столько комментариев, что они не сработают. Единственный консенсус в том, что это настолько сложно, что никто не сделал для него стандарта . Тем не менее, большинство принятых ответов в SO раскрывают «простые трюки», которые широко используются. Итак, для всех нас, таких как я, которые не являются экспертами, но хотят писать более безопасный код, поняв немного больше о сложности javascript, я постараюсь пролить немного света.
Прежде чем испачкать руки, позвольте мне уточнить 2 момента:
Object.assign
делает.Ответы с
for..in
илиObject.keys
вводят в заблуждениеСоздание глубокой копии кажется настолько простой и обычной практикой, что мы ожидаем найти однострочник или, по крайней мере, быстрый выигрыш с помощью простой рекурсии. Мы не ожидаем, что нам нужна библиотека или написать пользовательскую функцию из 100 строк.
Когда я впервые прочитал ответ Salakar в , я действительно думал , что я мог бы сделать лучше и проще (вы можете сравнить его с
Object.assign
наx={a:1}, y={a:{b:1}}
). Затем я прочитал ответ «8472» и подумал ... что так легко не уйти, улучшение уже полученных ответов не даст нам далеко.Давайте на мгновение оставим глубокое копирование и рекурсивность. Просто подумайте, как (неправильно) люди анализируют свойства, чтобы скопировать очень простой объект.
Object.keys
будет опускать собственные не перечисляемые свойства, собственные свойства с символьным ключом и все свойства прототипа. Это может быть хорошо, если ваши объекты не имеют ни одного из них. Но имейте в виду, что онObject.assign
обрабатывает собственные перечисляемые свойства с символьными ключами. Таким образом, ваша пользовательская копия потеряла свой цвет.for..in
предоставит свойства источника, его прототипа и всей цепочки прототипов, не желая этого (или не зная об этом). Ваша цель может получить слишком много свойств, смешивая свойства прототипа и собственные свойства.Если вы пишете функцию общего назначения , и вы не используете
Object.getOwnPropertyDescriptors
,Object.getOwnPropertyNames
,Object.getOwnPropertySymbols
илиObject.getPrototypeOf
, вы , скорее всего , делают это неправильно.Что нужно учесть, прежде чем писать свою функцию
Во-первых, убедитесь, что вы понимаете, что такое объект Javascript. В Javascript объект состоит из его собственных свойств и (родительского) объекта-прототипа. Объект-прототип в свою очередь состоит из его собственных свойств и объекта-прототипа. И так далее, определяя прототип цепочки.
Свойство - это пара ключей (
string
илиsymbol
) и дескриптора (value
илиget
/set
аксессора, и атрибутов типаenumerable
).Наконец, есть много типов объектов . Вы можете по-разному обрабатывать объект Object из объекта Date или объекта Function.
Итак, написав свою глубокую копию, вы должны ответить хотя бы на следующие вопросы:
В моем примере я считаю, что только
object Object
s являются глубокими , потому что другие объекты, созданные другими конструкторами, могут не подходить для углубленного анализа. Настроены из этого ТАК .И я сделал
options
объект, чтобы выбрать, что копировать (для демонстрации).Предлагаемая функция
Вы можете проверить это в этом поршне .
Это можно использовать так:
источник
Я использую lodash:
источник
_cloneDeep(value1).merge(value2)
Вот реализация TypeScript:
И юнит-тесты:
источник
Вот еще одно решение ES6, работающее с объектами и массивами.
источник
Пакет deepmerge npm является наиболее широко используемой библиотекой для решения этой проблемы: https://www.npmjs.com/package/deepmerge
источник
Я хотел бы представить довольно простую альтернативу ES5. Функция получает 2 параметра -
target
и ониsource
должны иметь тип «объект».Target
будет полученным объектом.Target
сохраняет все свои первоначальные свойства, но их значения могут быть изменены.случаи:
target
нетsource
собственности,target
получает его;target
естьsource
свойство иtarget
&source
не являются обоими объектами (3 случая из 4),target
свойство переопределяется;target
естьsource
свойство и оба они являются объектами / массивами (1 остающийся случай), то происходит рекурсия, объединяющая два объекта (или объединение двух массивов);Также учтите следующее :
Это предсказуемо, поддерживает примитивные типы, а также массивы и объекты. Кроме того, как мы можем объединить 2 объектов, я думаю , что мы можем объединить более 2 с помощью снижения функции.
взгляните на пример (и поиграйте с ним, если хотите) :
Есть ограничение - длина стека вызовов браузера. Современные браузеры выдадут ошибку на очень глубоком уровне рекурсии (например, тысячи вложенных вызовов). Также вы можете свободно обрабатывать ситуации, такие как массив + объект и т. Д., Добавляя новые условия и проверки типов.
источник
Если вы используете ImmutableJS, вы можете использовать
mergeDeep
:источник
Если библиотеки npm могут быть использованы в качестве решения, объектно-расширенный объект вашей компании действительно позволяет глубоко объединять объекты и настраивать / переопределять каждое действие слияния, используя знакомую функцию обратного вызова. Основная идея этого заключается не только в глубоком слиянии - что происходит со значением, когда два ключа совпадают ? Эта библиотека заботится об этом - когда два ключа сталкиваются,
object-merge-advanced
взвешивает типы, стремясь сохранить как можно больше данных после объединения:Ключ первого входного аргумента обозначен # 1, второй аргумент - # 2. В зависимости от каждого типа для значения ключа результата выбирается один. На диаграмме «объект» означает простой объект (не массив и т. Д.).
Когда ключи не конфликтуют, они все вводят результат.
Из вашего примера фрагмента, если вы использовали
object-merge-advanced
для объединения своего фрагмента кода:Его алгоритм рекурсивно обходит все входные ключи объекта, сравнивает и строит и возвращает новый объединенный результат.
источник
Следующая функция делает глубокую копию объектов, она охватывает копирование примитивов, массивов, а также объектов
источник
Простое решение с ES5 (перезаписать существующее значение):
источник
Большинство примеров здесь кажутся слишком сложными, я использую один из созданных мной TypeScript, думаю, он должен охватывать большинство случаев (я обрабатываю массивы как обычные данные, просто заменяя их).
То же самое в обычном JS, на всякий случай:
Вот мои тестовые примеры, чтобы показать, как вы могли бы использовать его
Пожалуйста, дайте мне знать, если вы думаете, что мне не хватает какой-то функциональности.
источник
Если вы хотите иметь один лайнер, не требуя огромной библиотеки, такой как lodash, я предлагаю вам использовать deepmerge . (
npm install deepmerge
)Затем вы можете сделать
получить
Хорошая вещь, это идет с наборами для TypeScript сразу. Это также позволяет объединять массивы . Это действительно универсальное решение.
источник
Мы можем использовать $ .extend (true, object1, object2) для глубокого слияния. Значение true обозначает слияние двух объектов рекурсивно, изменяя первый.
$ Простираться (правда, цель, объект)
источник
jQuery.isPlainObject()
. Это раскрывает сложность определения того, является ли что-то простым объектом, который большинство ответов здесь упускает из виду. Угадайте, на каком языке написан jQuery?Здесь прямое, простое решение, которое работает как
Object.assign
просто глубже и работает для массива, без каких-либо измененийпример
источник
У меня была эта проблема при загрузке кэшированного избыточного состояния. Если бы я просто загружал кэшированное состояние, я столкнулся бы с ошибками для новой версии приложения с обновленной структурой состояний.
Уже упоминалось, что lodash предлагает
merge
функцию, которую я использовал:источник
Многие ответы используют десятки строк кода или требуют добавления новой библиотеки в проект, но если вы используете рекурсию, это всего лишь 4 строки кода.
Обработка массивов: вышеприведенная версия заменяет старые значения массива новыми. Если вы хотите, чтобы он сохранил старые значения массива и добавил новые, просто добавьте
else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])
блок надelse
отчетливостью, и все готово.источник
Вот еще один, который я только что написал, который поддерживает массивы. Это их объединяет.
источник
Используйте эту функцию:
источник
Рамда, которая является хорошей библиотекой функций JavaScript, имеет mergeDeepLeft и mergeDeepRight. Любой из них работает очень хорошо для этой проблемы. Пожалуйста, ознакомьтесь с документацией здесь: https://ramdajs.com/docs/#mergeDeepLeft
Для конкретного рассматриваемого примера мы можем использовать:
источник
Модульный тест:
источник
Я нашел только двухстрочное решение для глубокого слияния в javascript. Дайте мне знать, как это работает для вас.
Temp объект напечатает {a: {b: 'd', e: 'f', x: 'y'}}
источник
merge({x:{y:{z:1}}}, {x:{y:{w:2}}})
. Il также не сможет обновить существующие значения в obj1, если у obj2 они тоже есть, например, с помощьюmerge({x:{y:1}}, {x:{y:2}})
.Иногда вам не нужно глубокое слияние, даже если вы так думаете. Например, если у вас есть конфигурация по умолчанию с вложенными объектами, и вы хотите глубоко расширить ее с помощью своей собственной конфигурации, вы можете создать класс для этого. Концепция очень проста:
Вы можете преобразовать его в функцию (не конструктор).
источник
Это дешевое глубокое слияние, в котором используется как можно меньше кода. Каждый источник перезаписывает предыдущее свойство, когда оно существует.
источник
Я использую следующую короткую функцию для глубокого объединения объектов.
Это прекрасно работает для меня.
Автор полностью объясняет, как это работает здесь.
источник