两个对象。assign和Object spread只做浅合并。

这个问题的一个例子:

// 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()相同。

有办法做到这一点吗?


当前回答

我试着写一个对象。基于Object的pollyfill的assignDeep。在mdn上赋值。

(ES5)

Object.assignDeep = function (target, varArgs) { // .length of function is 2 'use strict'; if (target == null) { // TypeError if undefined or null throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource != null) { // Skip over if undefined or null for (var nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { if (typeof to[nextKey] === 'object' && to[nextKey] && typeof nextSource[nextKey] === 'object' && nextSource[nextKey]) { Object.assignDeep(to[nextKey], nextSource[nextKey]); } else { to[nextKey] = nextSource[nextKey]; } } } } } return to; }; console.log(Object.assignDeep({},{a:{b:{c:1,d:1}}},{a:{b:{c:2,e:2}}}))

其他回答

当涉及到宿主对象或比值包更复杂的任何类型的对象时,这个问题就不那么简单了

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?

另一件需要记住的事情是:包含循环的对象图。这通常不难处理——简单地保留一组已经访问过的源对象——但经常被遗忘。

您可能应该编写一个深度合并函数,它只期望原始值和简单对象(结构化克隆算法最多可以处理的那些类型)作为合并源。如果遇到它不能处理的东西,或者只是通过引用而不是深度合并进行赋值,则抛出。

换句话说,没有一种适合所有人的算法,您要么必须使用自己的算法,要么寻找恰好涵盖您的用例的库方法。

使用这个函数:

merge(target, source, mutable = false) {
        const newObj = typeof target == 'object' ? (mutable ? target : Object.assign({}, target)) : {};
        for (const prop in source) {
            if (target[prop] == null || typeof target[prop] === 'undefined') {
                newObj[prop] = source[prop];
            } else if (Array.isArray(target[prop])) {
                newObj[prop] = source[prop] || target[prop];
            } else if (target[prop] instanceof RegExp) {
                newObj[prop] = source[prop] || target[prop];
            } else {
                newObj[prop] = typeof source[prop] === 'object' ? this.merge(target[prop], source[prop]) : source[prop];
            }
        }
        return newObj;
    }

我知道这是一个老问题,但在ES2015/ES6中我能想到的最简单的解决方案实际上很简单,使用Object.assign(),

希望这能有所帮助:

/**
 * 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);
}

使用示例:

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] } } }

你将在下面的答案中找到一个不可更改的版本。

注意,这将导致循环引用上的无限递归。这里有一些关于如何检测循环引用的很好的答案,如果你认为你会面临这个问题。

Ramda是一个很好的javascript函数库,它有mergeDeepLeft和mergeDeepRight。这些方法都能解决这个问题。请在这里查看文档:https://ramdajs.com/docs/#mergeDeepLeft

对于问题中的具体例子,我们可以使用:

import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}

我想介绍一个相当简单的ES5替代方案。该函数获得2个参数——目标和源,必须为“object”类型。Target将是结果对象。Target保留其所有原始属性,但它们的值可能会被修改。

function deepMerge(target, source) {
if(typeof target !== 'object' || typeof source !== 'object') return false; // target or source or both ain't objects, merging doesn't make sense
for(var prop in source) {
  if(!source.hasOwnProperty(prop)) continue; // take into consideration only object's own properties.
  if(prop in target) { // handling merging of two properties with equal names
    if(typeof target[prop] !== 'object') {
      target[prop] = source[prop];
    } else {
      if(typeof source[prop] !== 'object') {
        target[prop] = source[prop];
      } else {
        if(target[prop].concat && source[prop].concat) { // two arrays get concatenated
          target[prop] = target[prop].concat(source[prop]);
        } else { // two objects get merged recursively
          target[prop] = deepMerge(target[prop], source[prop]); 
        } 
      }  
    }
  } else { // new properties get added to target
    target[prop] = source[prop]; 
  }
}
return target;
}

例:

如果target没有source属性,则target获取source属性; 如果目标有source属性,而target & source没有 两个对象(4个中的3个),目标的属性被覆盖; 如果target确实有一个source属性,并且它们都是对象/数组(剩余1种情况),那么递归发生合并两个对象(或两个数组的连接);

还要考虑以下几点:

Array + obj = Array Obj + array = Obj Obj + Obj = Obj(递归合并) Array + Array = Array (concat)

它是可预测的,支持基本类型以及数组和对象。我们可以合并两个对象,我认为我们可以通过reduce函数合并两个以上的对象。

看一个例子(如果你想的话,也可以玩一下):

var a = { "a_prop": 1, "arr_prop": [4, 5, 6], "obj": { "a_prop": { "t_prop": 'test' }, "b_prop": 2 } }; var b = { "a_prop": 5, "arr_prop": [7, 8, 9], "b_prop": 15, "obj": { "a_prop": { "u_prop": false }, "b_prop": { "s_prop": null } } }; function deepMerge(target, source) { if(typeof target !== 'object' || typeof source !== 'object') return false; for(var prop in source) { if(!source.hasOwnProperty(prop)) continue; if(prop in target) { if(typeof target[prop] !== 'object') { target[prop] = source[prop]; } else { if(typeof source[prop] !== 'object') { target[prop] = source[prop]; } else { if(target[prop].concat && source[prop].concat) { target[prop] = target[prop].concat(source[prop]); } else { target[prop] = deepMerge(target[prop], source[prop]); } } } } else { target[prop] = source[prop]; } } return target; } console.log(deepMerge(a, b));

有一个限制-浏览器的调用堆栈长度。现代浏览器会在一些真正深层的递归中抛出错误(想想成千上万的嵌套调用)。此外,您还可以自由地处理像数组+对象等情况,因为您希望添加新的条件和类型检查。