How to deep merge instead of shallow merge?

Both Object.assign and Object spread only do a shallow merge.

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 operator only goes one level deep. This is the same with Object.assign() .

Is there a way to do this?


Does anybody know if deep merging exists in the ES6/ES7 spec?

No, it does not.


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(),

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.


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? 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.

    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.

    链接地址: http://www.djcxy.com/p/4694.html

    上一篇: JavaScript中的对象比较

    下一篇: 如何深度合并而不是浅层合并?