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

有办法做到这一点吗?


当前回答

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

其他回答

简单递归解

使用对象。条目,遍历其中一个对象。如果条目不存在,则添加该条目;如果条目是对象,则递归。

常量x = {a: {a: 1}} Const y = {a: {b: 1}} JSON.parse(JSON.stringify(y)) const mergeIntoZ = (firstObj, secondObj) => { Object.entries (firstObj) .forEach(([key, value]) => { if (secondObj[key] === undefined) { secondObj[key] = value } else if (typeof value === 'object') { mergeIntoZ (firstObj(关键),secondObj[主要]) } }) } mergeIntoZ (x, z) console.log (z)

这里的大多数示例似乎太复杂了,我使用的是我创建的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' } } });
    });
  });
});

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

适用于对象和数组的Vanilla Script解决方案:

const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }

function deepmerge() {
  merge = function () {
    let target = arguments[0];
    for (let i = 1; i < arguments.length ; i++) {
      let arr = arguments[i];
            for (let k in arr) {
         if (Array.isArray(arr[k])) {
            if (target[k] === undefined) {            
                 target[k] = [];
            }            
            target[k] = [...new Set(target[k].concat(...arr[k]))];
         } else if (typeof arr[k] === 'object') {
            if (target[k] === undefined) {            
                 target[k] = {};
            }
            target[k] = merge(target[k], arr[k]);
         } else {
              target[k] = arr[k];         
         }
      }
    }
    return target;
  }
  return merge(...arguments);
}
console.log(deepmerge(x,y));

输出:

{
  a: {
    a: 1,
    b: 1
  }
}

我把这里所有的答案都看了一遍,然后拼凑出了一个我自己的答案。现有的大多数答案都不是我想要的方式。

这对于2021年来说是相当可怕的,所以任何改善的建议,我都洗耳恭听!

这是在Typescript中

type Props = Record<string, any>

export const deepMerge = (target: Props, ...sources: Props[]): Props => {
  if (!sources.length) {
    return target
  }

  Object.entries(sources.shift() ?? []).forEach(([key, value]) => {
    if (!target[key]) {
      Object.assign(target, { [key]: {} })
    }

    if (
      value.constructor === Object ||
      (value.constructor === Array && value.find(v => v.constructor === Object))
    ) {
      deepMerge(target[key], value)
    } else if (value.constructor === Array) {
      Object.assign(target, {
        [key]: value.find(v => v.constructor === Array)
          ? target[key].concat(value)
          : [...new Set([...target[key], ...value])],
      })
    } else {
      Object.assign(target, { [key]: value })
    }
  })

  return target
}

平面数组使用[…]删除重复值。新的(…)]。

嵌套数组使用concat连接。

使用这个函数:

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