本文翻译自:How to deep merge instead of shallow merge?
Both Object.assign and Object spread only do a shallow merge. Object.assign和Object Spread都仅进行浅合并。
An example of the problem: 问题的一个示例:
// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }
The output is what you'd expect. 输出是您期望的。 However if I try this: 但是,如果我尝试这样做:
// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }
Instead of 代替
{ a: { a: 1, b: 1 } }
you get 你得到
{ a: { b: 1 } }
x is completely overwritten because the spread syntax only goes one level deep. x完全被覆盖,因为传播语法仅深入了一层。 This is the same with Object.assign()
. 这与Object.assign()
相同。
Is there a way to do this? 有没有办法做到这一点?
#1楼
参考:https://stackoom.com/question/1TdD6/如何深层合并而不是浅层合并
#2楼
The problem is non-trivial when it comes to host objects or any kind of object that's more complex than a bag of values 当涉及宿主对象或比一袋值更复杂的任何类型的对象时,问题都不是简单的
- do you invoke a getter to obtain a value or do you copy over the property descriptor? 调用获取方法以获取值还是复制属性描述符?
- what if the merge target has a setter (either own property or in its prototype chain)? 如果合并目标具有设置器(自己的属性或其原型链中的),该怎么办? Do you consider the value as already-present or call the setter to update the current value? 您认为该值已经存在还是调用设置器来更新当前值?
- do you invoke own-property functions or copy them over? 您会调用自有功能还是将其复制过来? What if they're bound functions or arrow functions depending on something in their scope chain at the time they were defined? 如果它们是绑定函数或箭头函数,取决于定义它们时作用域链中的某物怎么办?
- what if it's something like a DOM node? 如果它像DOM节点该怎么办? You certainly don't want to treat it as simple object and just deep-merge all its properties over into 您当然不希望将其视为简单对象,而只是将其所有属性深层合并到
- how to deal with "simple" structures like arrays or maps or sets? 如何处理“简单”结构,如数组或映射或集合? Consider them already-present or merge them too? 认为它们已经存在或合并了吗?
- how to deal with non-enumerable own properties? 如何处理不可数的自有财产?
- what about new subtrees? 那新的子树呢? Simply assign by reference or deep clone? 只是通过引用分配还是进行深克隆?
- how to deal with frozen/sealed/non-extensible objects? 如何处理冻结/密封/不可扩展的物体?
Another thing to keep in mind: Object graphs that contain cycles. 要记住的另一件事:包含循环的对象图。 It's usually not difficult to deal with - simply keep a Set
of already-visited source objects - but often forgotten. 通常不难处理-只需保留一Set
已经访问过的源对象-但通常会被遗忘。
You probably should write a deep-merge function that only expects primitive values and simple objects - at most those types that the structured clone algorithm can handle - as merge sources. 您可能应该编写一个深度合并功能,该功能仅将原始值和简单对象(最多为结构化克隆算法可以处理的那些类型)作为合并源。 Throw if it encounters anything it cannot handle or just assign by reference instead of deep merging. 如果遇到任何无法处理的事件或仅通过引用分配而不是深度合并,则抛出该异常。
In other words, there is no one-size-fits-all algorithm, you either have to roll your own or look for a library method that happens to cover your use-cases. 换句话说,没有一种万能的算法,您要么必须自己滚动,要么寻找碰巧可以满足用例的库方法。
#3楼
I know this is a bit of an old issue but the easiest solution in ES2015/ES6 I could come up with was actually quite simple, using Object.assign(), 我知道这是一个老问题了,但是我想出的ES2015 / ES6最简单的解决方案实际上非常简单,使用Object.assign(),
Hopefully this helps: 希望这会有所帮助:
/**
* Simple object check.
* @param item
* @returns {boolean}
*/
export function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
/**
* Deep merge two objects.
* @param target
* @param ...sources
*/
export function mergeDeep(target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
Example usage: 用法示例:
mergeDeep(this, { a: { b: { c: 123 } } });
// or
const merged = mergeDeep({a: 1}, { b : { c: { d: { e: 12345}}}});
console.dir(merged); // { a: 1, b: { c: { d: [Object] } } }
You'll find an immutable version of this in the answer below. 您将在下面的答案中找到它的不变版本。
Note that this will lead to infinite recursion on circular references. 请注意,这将导致循环引用上的无限递归。 There's some great answers on here on how to detect circular references if you think you'd face this issue. 如果您认为自己会遇到此问题,那么如何检测循环引用在这里有一些很好的答案。
#4楼
Does anybody know if deep merging exists in the ES6/ES7 spec? 有人知道ES6 / ES7规范中是否存在深度合并?
No, it does not. 不,不是的。
#5楼
Here is an immutable (does not modify the inputs) version of @Salakar's answer. 这是@Salakar答案的一个不变的版本(不修改输入内容)。 Useful if you're doing functional programming type stuff. 如果您正在执行函数式编程类型的东西,则很有用。
export function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}
export default function mergeDeep(target, source) {
let output = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
Object.keys(source).forEach(key => {
if (isObject(source[key])) {
if (!(key in target))
Object.assign(output, { [key]: source[key] });
else
output[key] = mergeDeep(target[key], source[key]);
} else {
Object.assign(output, { [key]: source[key] });
}
});
}
return output;
}
#6楼
I make this method for deep assign using es6. 我将这种方法用于使用es6进行深度分配。
function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item) && item !== null)
}
function deepAssign(...objs) {
if (objs.length < 2) {
throw new Error('Need two or more objects to merge')
}
const target = objs[0]
for (let i = 1; i < objs.length; i++) {
const source = objs[i]
Object.keys(source).forEach(prop => {
const value = source[prop]
if (isObject(value)) {
if (target.hasOwnProperty(prop) && isObject(target[prop])) {
target[prop] = deepAssign(target[prop], value)
} else {
target[prop] = value
}
} else if (Array.isArray(value)) {
if (target.hasOwnProperty(prop) && Array.isArray(target[prop])) {
const targetArray = target[prop]
value.forEach((sourceItem, itemIndex) => {
if (itemIndex < targetArray.length) {
const targetItem = targetArray[itemIndex]
if (Object.is(targetItem, sourceItem)) {
return
}
if (isObject(targetItem) && isObject(sourceItem)) {
targetArray[itemIndex] = deepAssign(targetItem, sourceItem)
} else if (Array.isArray(targetItem) && Array.isArray(sourceItem)) {
targetArray[itemIndex] = deepAssign(targetItem, sourceItem)
} else {
targetArray[itemIndex] = sourceItem
}
} else {
targetArray.push(sourceItem)
}
})
} else {
target[prop] = value
}
} else {
target[prop] = value
}
})
}
return target
}