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

有办法做到这一点吗?


当前回答

function isObject(obj) {
    return obj !== null && typeof obj === 'object';
}
const isArray = Array.isArray;

function isPlainObject(obj) {
    return isObject(obj) && (
        obj.constructor === Object  // obj = {}
        || obj.constructor === undefined // obj = Object.create(null)
    );
}

function mergeDeep(target, ...sources){
    if (!sources.length) return target;
    const source = sources.shift();

    if (isPlainObject(source) || isArray(source)) {
        for (const key in source) {
            if (isPlainObject(source[key]) || isArray(source[key])) {
                if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
                    target[key] = {};
                }else if (isArray(source[key]) && !isArray(target[key])) {
                    target[key] = [];
                }
                mergeDeep(target[key], source[key]);
            } else if (source[key] !== undefined && source[key] !== '') {
                target[key] = source[key];
            }
        }
    }

    return mergeDeep(target, ...sources);
}

// test...
var source = {b:333};
var source2 = {c:32, arr: [33,11]}
var n = mergeDeep({a:33}, source, source2);
source2.arr[1] = 22;
console.log(n.arr); // out: [33, 11]

其他回答

有一些维护良好的库已经做到了这一点。npm注册表中的一个例子是merge-deep

function isObject(obj) {
    return obj !== null && typeof obj === 'object';
}
const isArray = Array.isArray;

function isPlainObject(obj) {
    return isObject(obj) && (
        obj.constructor === Object  // obj = {}
        || obj.constructor === undefined // obj = Object.create(null)
    );
}

function mergeDeep(target, ...sources){
    if (!sources.length) return target;
    const source = sources.shift();

    if (isPlainObject(source) || isArray(source)) {
        for (const key in source) {
            if (isPlainObject(source[key]) || isArray(source[key])) {
                if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
                    target[key] = {};
                }else if (isArray(source[key]) && !isArray(target[key])) {
                    target[key] = [];
                }
                mergeDeep(target[key], source[key]);
            } else if (source[key] !== undefined && source[key] !== '') {
                target[key] = source[key];
            }
        }
    }

    return mergeDeep(target, ...sources);
}

// test...
var source = {b:333};
var source2 = {c:32, arr: [33,11]}
var n = mergeDeep({a:33}, source, source2);
source2.arr[1] = 22;
console.log(n.arr); // out: [33, 11]

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

它叫lodash.clonedeep

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

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

在ReactJS中,用法是

import cloneDeep from 'lodash/cloneDeep';

const newObject = cloneDeep(oldObject);

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

有时候你并不需要深度合并,即使你这样认为。例如,如果您有一个带有嵌套对象的默认配置,并且您希望用自己的配置对其进行深入扩展,您可以为此创建一个类。概念很简单:

function AjaxConfig(config) {

  // Default values + config

  Object.assign(this, {
    method: 'POST',
    contentType: 'text/plain'
  }, config);

  // Default values in nested objects

  this.headers = Object.assign({}, this.headers, { 
    'X-Requested-With': 'custom'
  });
}

// Define your config

var config = {
  url: 'https://google.com',
  headers: {
    'x-client-data': 'CI22yQEI'
  }
};

// Extend the default values with your own
var fullMergedConfig = new AjaxConfig(config);

// View in DevTools
console.log(fullMergedConfig);

您可以将其转换为函数(而不是构造函数)。

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

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