两个对象。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()相同。

有办法做到这一点吗?


当前回答

我知道这是一个老问题,但在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] } } }

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

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

其他回答

deepmerge npm包似乎是解决这个问题使用最广泛的库: https://www.npmjs.com/package/deepmerge

如果你想要一个单行程序,而不需要像lodash那样庞大的库,我建议你使用deepmerge (npm install deepmerge)或deepmerge-ts (npm install deepmerge-ts)。

deepmerge也为TypeScript提供了类型,并且更加稳定(因为它比较老),但是deepmerge-ts也可用于Deno,并且从设计上看更快,尽管顾名思义是用TypeScript编写的。

一旦导入就可以了

deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });

得到

{ a: 2, b: 2, c: 3, d: 3 }

这对于复杂的对象和数组非常有效。这是一个真正的全面解决方案。

我使用lodash:

import _ = require('lodash');
value = _.merge(value1, value2);

这里的大多数示例似乎太复杂了,我使用的是我创建的TypeScript中的一个,我认为它应该涵盖大多数情况(我将数组作为常规数据处理,只是替换它们)。

const isObject = (item: any) => typeof item === 'object' && !Array.isArray(item);

export const merge = <A = Object, B = Object>(target: A, source: B): A & B => {
  const isDeep = (prop: string) =>
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...(target as Object),
    ...(replaced as Object)
  } as A & B;
};

在纯JS中也是如此,以防万一:

const isObject = item => typeof item === 'object' && !Array.isArray(item);

const merge = (target, source) => {
  const isDeep = prop => 
    isObject(source[prop]) && target.hasOwnProperty(prop) && isObject(target[prop]);
  const replaced = Object.getOwnPropertyNames(source)
    .map(prop => ({ [prop]: isDeep(prop) ? merge(target[prop], source[prop]) : source[prop] }))
    .reduce((a, b) => ({ ...a, ...b }), {});

  return {
    ...target,
    ...replaced
  };
};

下面是我的测试用例,向您展示如何使用它

describe('merge', () => {
  context('shallow merges', () => {
    it('merges objects', () => {
      const a = { a: 'discard' };
      const b = { a: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test' });
    });
    it('extends objects', () => {
      const a = { a: 'test' };
      const b = { b: 'test' };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: 'test' });
    });
    it('extends a property with an object', () => {
      const a = { a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
    it('replaces a property with an object', () => {
      const a = { b: 'whatever', a: 'test' };
      const b = { b: { c: 'test' } };
      expect(merge(a, b)).to.deep.equal({ a: 'test', b: { c: 'test' } });
    });
  });

  context('deep merges', () => {
    it('merges objects', () => {
      const a = { test: { a: 'discard', b: 'test' }  };
      const b = { test: { a: 'test' } } ;
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends objects', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: 'test' } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: 'test' } });
    });
    it('extends a property with an object', () => {
      const a = { test: { a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
    it('replaces a property with an object', () => {
      const a = { test: { b: 'whatever', a: 'test' } };
      const b = { test: { b: { c: 'test' } } };
      expect(merge(a, b)).to.deep.equal({ test: { a: 'test', b: { c: 'test' } } });
    });
  });
});

如果你认为我缺少一些功能,请告诉我。

我发现只有2行解决方案得到深度合并在javascript。一定要告诉我你的结果。

const obj1 = { a: { b: "c", x: "y" } }
const obj2 = { a: { b: "d", e: "f" } }
temp = Object.assign({}, obj1, obj2)
Object.keys(temp).forEach(key => {
    temp[key] = (typeof temp[key] === 'object') ? Object.assign(temp[key], obj1[key], obj2[key]) : temp[key])
}
console.log(temp)

临时对象将打印{a: {b: 'd', e: 'f', x: 'y'}}