两个对象。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.prototype.merge = function(object) {
    for (const key in object) {
        if (object.hasOwnProperty(key)) {
            if (typeof this[key] === "object" && typeof object[key] === "object") {
                this[key].merge(object[key]);

                continue;
            }

            this[key] = object[key];
        }
    }

    return this;
}

我希望这能帮助那些努力理解正在发生的事情的人。我在这里看到了很多无意义的变量。

谢谢

其他回答

ES5的一个简单解决方案(覆盖现有值):

function merge(current, update) { Object.keys(update).forEach(function(key) { // if update[key] exist, and it's not a string or array, // we go in one level deeper if (current.hasOwnProperty(key) && typeof current[key] === 'object' && !(current[key] instanceof Array)) { merge(current[key], update[key]); // if update[key] doesn't exist in current, or it's a string // or array, then assign/overwrite current[key] to update[key] } else { current[key] = update[key]; } }); return current; } var x = { a: { a: 1 } } var y = { a: { b: 1 } } console.log(merge(x, y));

有办法做到这一点吗?

如果npm库可以作为一个解决方案,你的object-merge-advanced允许深度合并对象,并使用一个熟悉的回调函数定制/覆盖每一个合并操作。它的主要思想不仅仅是深度合并——当两个键相同时,值会发生什么变化?这个库负责处理这个问题——当两个键冲突时,object-merge-advanced会对类型进行加权,目的是在合并后保留尽可能多的数据:

第一个输入参数的键标记为#1,第二个参数的键标记为- #2。根据每种类型,将为结果键的值选择一个类型。在图表中,“对象”指的是普通对象(不是数组等)。

当键不冲突时,它们都输入结果。

在你的示例代码片段中,如果你使用object-merge-advanced来合并你的代码片段:

const mergeObj = require("object-merge-advanced");
const x = { a: { a: 1 } };
const y = { a: { b: 1 } };
const res = console.log(mergeObj(x, y));
// => res = {
//      a: {
//        a: 1,
//        b: 1
//      }
//    }

它的算法递归遍历所有输入对象键,比较和构建并返回新的合并结果。

我的用例是将默认值合并到配置中。如果我的组件接受一个具有深度嵌套结构的配置对象,并且我的组件定义了默认配置,那么我希望在配置中为未提供的所有配置选项设置默认值。

使用示例:

export default MyComponent = ({config}) => {
  const mergedConfig = mergeDefaults(config, {header:{margins:{left:10, top: 10}}});
  // Component code here
}

这允许我传递一个空配置或空配置,或一个部分配置,并让所有未配置的值回落到它们的默认值。

我的mergeDefaults实现如下所示:

export default function mergeDefaults(config, defaults) {
  if (config === null || config === undefined) return defaults;
  for (var attrname in defaults) {
    if (defaults[attrname].constructor === Object) config[attrname] = mergeDefaults(config[attrname], defaults[attrname]);
    else if (config[attrname] === undefined) config[attrname] = defaults[attrname];
  }
  return config;
}


这些是单元测试

import '@testing-library/jest-dom/extend-expect';
import mergeDefaults from './mergeDefaults';

describe('mergeDefaults', () => {
  it('should create configuration', () => {
    const config = mergeDefaults(null, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(10);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('default2');
  });
  it('should fill configuration', () => {
    const config = mergeDefaults({}, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(10);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('default2');
  });
  it('should not overwrite configuration', () => {
    const config = mergeDefaults({ a: 12, b: { c: 'config1', d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' } });
    expect(config.a).toStrictEqual(12);
    expect(config.b.c).toStrictEqual('config1');
    expect(config.b.d).toStrictEqual('config2');
  });
  it('should merge configuration', () => {
    const config = mergeDefaults({ a: 12, b: { d: 'config2' } }, { a: 10, b: { c: 'default1', d: 'default2' }, e: 15 });
    expect(config.a).toStrictEqual(12);
    expect(config.b.c).toStrictEqual('default1');
    expect(config.b.d).toStrictEqual('config2');
    expect(config.e).toStrictEqual(15);
  });
});

有一个lodash包专门处理对象的深度克隆。这样做的好处是不需要包含整个lodash库。

它叫lodash.clonedeep

在nodejs中,这种用法是这样的

var cloneDeep = require('lodash.clonedeep');
 
const newObject = cloneDeep(oldObject);

在ReactJS中,用法是

import cloneDeep from 'lodash/cloneDeep';

const newObject = cloneDeep(oldObject);

查看这里的文档。如果您对它的工作原理感兴趣,请查看这里的源文件

这里是@Salakar的答案的一个不可变(不修改输入)版本。如果你在做函数式编程,这很有用。

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