如何深度合并而不是浅合并?

Mik*_*ike 289 javascript spread-syntax

无论Object.assign对象传播只能做一浅合并.

问题的一个例子:

// No object nesting
const x = { a: 1 }
const y = { b: 1 }
const z = { ...x, ...y } // { a: 1, b: 1 }
Run Code Online (Sandbox Code Playgroud)

输出是你所期望的.但是如果我试试这个:

// Object nesting
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = { ...x, ...y } // { a: { b: 1 } }
Run Code Online (Sandbox Code Playgroud)

代替

{ a: { a: 1, b: 1 } }
Run Code Online (Sandbox Code Playgroud)

你得到

{ a: { b: 1 } }
Run Code Online (Sandbox Code Playgroud)

x被完全覆盖,因为扩展语法只有一个深度.这是一样的Object.assign().

有没有办法做到这一点?

小智 305

有人知道ES6/ES7规范中是否存在深度合并?

不,不是的.

  • 此答案不再适用于此问题 - 应更新或删除 (20认同)
  • 请查看编辑历史记录.当我回答这个问题时,问题是*是否有人知道ES6/ES7规范中是否存在深度合并?*. (14认同)

Sal*_*kar 154

我知道这是一个老问题,但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);
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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] } } }
Run Code Online (Sandbox Code Playgroud)

您将在下面的答案中找到不可变版本.

请注意,这将导致循环引用的无限递归.如果您认为自己面临这个问题,那么有关如何检测循环引用的一些很好的答案.

  • ...和`target[key] = source[key]` 而不是`Object.assign(target, { [key]: source[key] });` (3认同)
  • 这个例子似乎来自这里https://blog.devgenius.io/how-to-deep-merge-javascript-objects-12a7235f5573,其中包含代码的完整解释。 (3认同)
  • 如果您的对象图包含会导致无限递归的循环 (2认同)
  • 为什么要这样写:Object.assign(target,{[key]:{}})),如果它只是`target [key] = {}`呢? (2认同)
  • 这不支持`target`中的任何非普通对象。例如,`mergeDeep({a:3},{a:{b:4}}))会产生一个扩展的`Number`对象,这显然是不希望的。同样,“ isObject”不接受数组,但是接受任何其他本机对象类型,例如“ Date”,不应进行深度复制。 (2认同)

the*_*472 95

当涉及到宿主对象或任何比一包价值更复杂的对象时,问题就不那么重要了

  • 你调用getter获取值还是复制属性描述符?
  • 如果合并目标有一个setter(自己的属性或其原型链)怎么办?您是否认为该值已存在或调用setter来更新当前值?
  • 你调用自己的属性函数或复制它们?如果它们在定义时根据其范围链中的某些内容绑定了函数或箭头函数,该怎么办?
  • 如果它像DOM节点那样呢?您当然不希望将其视为简单对象,只是深入合并其所有属性
  • 如何处理像数组或地图或集合这样的"简单"结构?考虑他们已经存在或合并他们?
  • 如何处理不可枚举的自有属性?
  • 那些新的子树呢?只需通过引用或深度克隆分配?
  • 如何处理冷冻/密封/不可扩展的物体?

另外要记住的是:包含循环的对象图.处理通常并不困难 - 只需保留Set已经访问过的源对象 - 但经常被遗忘.

您可能应该编写一个深度合并函数,它只需要原始值和简单对象 - 最多是结构化克隆算法可以处理的那些类型- 作为合并源.抛出如果它遇到任何它无法处理或只是通过引用分配而不是深度合并.

换句话说,没有一个适合所有人的算法,你要么自己动手,要么寻找恰好涵盖你的用例的库方法.

  • 您提出了许多好的问题,我希望看到您的建议得到实施。所以我尝试在下面制作一个。可以请您看一下并发表评论吗?/sf/answers/3400567831/ (3认同)
  • V8开发人员不能实现安全的"文档状态"转移的借口 (2认同)

And*_*son 95

你可以使用Lodash合并:

var object = {
  'a': [{ 'b': 2 }, { 'd': 4 }]
};

var other = {
  'a': [{ 'c': 3 }, { 'e': 5 }]
};

_.merge(object, other);
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
Run Code Online (Sandbox Code Playgroud)

  • 添加依赖项并不美观 (18认同)
  • 结果不应该是`{'a':[{'b':2},{'c':3},{'d':4},{'e':5}]}`? (8认同)
  • 嘿,这是最简单,最美丽的解决方案.Lodash太棒了,他们应该将它作为核心js对象包含在内 (6认同)
  • @stackers 重新发明轮子并不美好 (6认同)
  • 结果`{'a':[{'b':2,'c':3},{'d':4,'e':5}]}`是正确的,因为我们正在合并一个元素阵列.`object.a`的元素`0`是`{b:2}`,`other.a`的元素`0`是`{c:3}`.当这两个被合并因为它们具有相同的数组索引时,结果是`{'b':2,'c':3}`,这是新对象中的元素"0". (3认同)
  • 我更喜欢[这个](https://github.com/TehShrike/deepmerge),它的 gzip 压缩后小了 6 倍。 (2认同)
  • @J.Hesters 为了实现您所描述的目标,lodash 还有另一种方法:[mergeWith](https://lodash.com/docs/4.17.15#mergeWith) (2认同)

CpI*_*ILL 56

这是@ 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 它是一个计算属性名,第一个将使用`key`的值作为属性名,后者将使"key"成为属性名.请参阅:http://es6-features.org/#ComputedPropertyNames (3认同)
  • 在`isObject`中你最后不需要检查`&& item!== null`,因为该行以`item &&`开头,不是吗? (2认同)
  • 如果source嵌套的子对象比目标更深,那些对象仍会在`mergedDeep`的输出中引用相同的值(我认为).例如`const target = {a:1}; const source = {b:{c:2}}; const merged = mergeDeep(target,source);``merged.bc; // 2`` source.bc = 3;``merged.bc; // 3`这是一个问题吗?它不会改变输入,但输入的任何未来突变都可能使输出发生变异,反之亦然,突变输出变异输入.但是,对于它的价值,ramda的`R.merge()`具有相同的行为. (2认同)

jhi*_*dle 31

由于此问题仍然有效,这是另一种方法:

  • ES6/2015
  • 不可变(不修改原始对象)
  • 处理数组(连接它们)

/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
*
* @param {...object} objects - Objects to merge
* @returns {object} New object with merged key/values
*/
function mergeDeep(...objects) {
  const isObject = obj => obj && typeof obj === 'object';
  
  return objects.reduce((prev, obj) => {
    Object.keys(obj).forEach(key => {
      const pVal = prev[key];
      const oVal = obj[key];
      
      if (Array.isArray(pVal) && Array.isArray(oVal)) {
        prev[key] = pVal.concat(...oVal);
      }
      else if (isObject(pVal) && isObject(oVal)) {
        prev[key] = mergeDeep(pVal, oVal);
      }
      else {
        prev[key] = oVal;
      }
    });
    
    return prev;
  }, {});
}

// Test objects
const obj1 = {
  a: 1,
  b: 1, 
  c: { x: 1, y: 1 },
  d: [ 1, 1 ]
}
const obj2 = {
  b: 2, 
  c: { y: 2, z: 2 },
  d: [ 2, 2 ],
  e: 2
}
const obj3 = mergeDeep(obj1, obj2);

// Out
console.log(obj3);
Run Code Online (Sandbox Code Playgroud)

  • 唯一数组的替代 es6 解决方案。将 `prev[key] = pVal.concat(...oVal);` 更改为 `prev[key] = [...new Set([...oVal, ...pVal])];` 参考:https ://stackoverflow.com/a/9229821/6671505 (6认同)
  • 要使数组唯一,您可以将 `prev[key] = pVal.concat(...oVal);` 更改为 `prev[key] = [...pVal, ...oVal].filter((element, index , 数组) => array.indexOf(element) === index);` (4认同)
  • 辉煌。这也演示了数组被合并,这正是我一直在寻找的。 (2认同)

Rap*_*Mex 25

我知道已经有很多答案,并且有很多评论认为它们不起作用.唯一的共识是它很复杂,没有人为它制定标准.然而,SO中大多数公认的答案暴露了广泛使用的"简单技巧".因此,对于像我这样没有专家但想通过掌握更多关于javascript复杂性来编写更安全代码的所有人,我会试着解释一下.

在弄清楚之前,让我澄清2点:

  • [免责声明]我提出了一个函数,它解决了我们如何深入循环javascript对象的副本,并说明了通常太短暂评论的内容.它不是生产准备好的.为了清楚起见,我有意不考虑其他考虑因素,如循环对象(通过集合或非冲突符号属性跟踪),复制引用值或深度克隆,不可变目标对象(深度克隆再次?),逐个案例研究每种类型的对象,通过访问器获取/设置属性...此外,我没有测试性能 - 尽管它很重要 - 因为它也不是重点.
  • 我将使用复制分配术语而不是合并.因为在我看来,合并是保守的,并且应该在冲突时失败.在这里,当发生冲突时,我们希望源覆盖目标.喜欢Object.assign.

答案有for..inObject.keys有误导性

制作一份深刻的副本似乎是如此基本和通用的做法,我们期望通过简单的递归找到一个单行或至少快速获胜.我们不希望我们需要一个库或编写100行的自定义函数.

当我第一次读到Salakar的回答,我真的以为我可以做的更好,更简单的(你可以用比较Object.assignx={a:1}, y={a:{b:1}}).然后我读了8472的回答,我想......没有那么容易逃脱,改善已经给出的答案不会让我们走得太远.

让我们快速复制和递归.只考虑人们如何(错误地)解析属性来复制一个非常简单的对象.

const y = Object.create(
    { proto : 1 },
    { a: { enumerable: true, value: 1},
      [Symbol('b')] : { enumerable: true, value: 1} } )

Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied

((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!

((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!
Run Code Online (Sandbox Code Playgroud)

Object.keys将省略自己的非可枚举属性,拥有符号键控属性和所有原型的属性.如果您的对象没有任何这些对象可能没问题.但要记住,Object.assign处理自己的符号键可枚举属性.所以你的自定义副本失去了它的绽放

for..in将提供源,其原型和完整原型链的属性,而无需您(或知道它).您的目标最终可能会有太多属性,混合原型属性和自己的属性.

如果你正在写一个通用的功能,你不使用Object.getOwnPropertyDescriptors,Object.getOwnPropertyNames,Object.getOwnPropertySymbolsObject.getPrototypeOf,你很可能就错了.

在编写函数之前需要考虑的事项

首先,确保您了解Javascript对象是什么.在Javascript中,对象由其自己的属性和(父)原型对象组成.原型对象又由它自己的属性和原型对象组成.等等,定义原型链.

属性是一对键(stringsymbol)和描述符(valueget/ set访问器,以及属性enumerable).

最后,有许多类型的对象.您可能希望以不同的方式处理来自对象Date或对象Function的对象Object.

所以,写你的深层副本,你应该至少回答这些问题:

  1. 我认为什么深(适合递归查找)或扁平?
  2. 我想要复制哪些属性?(可枚举/不可枚举,字符串键控/符号键控,自己的属性/原型自己的属性,值/描述符......)

对于我的例子,我认为只有object Objects 很深,因为其他构造函数创建的其他对象可能不适合深入查看.从这个SO定制.

function toType(a) {
    // Get fine type (object, array, function, null, error, date ...)
    return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}

function isDeepObject(obj) {
    return "Object" === toType(obj);
}
Run Code Online (Sandbox Code Playgroud)

我做了一个options对象来选择要复制的内容(用于演示目的).

const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};
Run Code Online (Sandbox Code Playgroud)

拟议的职能

你可以在这个plunker中测试它.

function deepAssign(options) {
    return function deepAssignWithOptions (target, ...sources) {
        sources.forEach( (source) => {

            if (!isDeepObject(source) || !isDeepObject(target))
                return;

            // Copy source's own properties into target's own properties
            function copyProperty(property) {
                const descriptor = Object.getOwnPropertyDescriptor(source, property);
                //default: omit non-enumerable properties
                if (descriptor.enumerable || options.nonEnum) {
                    // Copy in-depth first
                    if (isDeepObject(source[property]) && isDeepObject(target[property]))
                        descriptor.value = deepAssign(options)(target[property], source[property]);
                    //default: omit descriptors
                    if (options.descriptors)
                        Object.defineProperty(target, property, descriptor); // shallow copy descriptor
                    else
                        target[property] = descriptor.value; // shallow copy value only
                }
            }

            // Copy string-keyed properties
            Object.getOwnPropertyNames(source).forEach(copyProperty);

            //default: omit symbol-keyed properties
            if (options.symbols)
                Object.getOwnPropertySymbols(source).forEach(copyProperty);

            //default: omit prototype's own properties
            if (options.proto)
                // Copy souce prototype's own properties into target prototype's own properties
                deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
                    Object.getPrototypeOf(target),
                    Object.getPrototypeOf(source)
                );

        });
        return target;
    }
}
Run Code Online (Sandbox Code Playgroud)

这可以像这样使用:

const x = { a: { a: 1 } },
      y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }
Run Code Online (Sandbox Code Playgroud)


mod*_*diX 25

如果您想拥有一个单行而不需要像 lodash 这样的庞大库,我建议您使用deepmerge。( npm install deepmerge)

然后,你可以做

deepmerge({ a: 1, b: 2, c: 3 }, { a: 2, d: 3 });
Run Code Online (Sandbox Code Playgroud)

要得到

{ a: 2, b: 2, c: 3, d: 3 }
Run Code Online (Sandbox Code Playgroud)

这适用于复杂的对象和数组。好消息是它立即带有 TypeScript 的类型。这是一个真正的全能解决方案。

  • 您不必需要整个 lodash 库。您可以只需要您需要的部分:`const merge = require('lodash.merge');` (4认同)
  • 这个应该被接受的答案 (3认同)
  • 一直在寻找几个小时,这挽救了这一天,能够合并深层物体,正如你所说的全能者,干杯! (2认同)

Jef*_*ian 10

我用lodash:

import _ = require('lodash');
value = _.merge(value1, value2);
Run Code Online (Sandbox Code Playgroud)

  • 请注意,合并会改变对象,如果你想要一些不会改变对象的东西,那么`_cloneDeep(value1).merge(value2)` (3认同)
  • @geckos您可以进行_.merge({},value1,value2) (2认同)

per*_*mon 10

在这里,直截了当;

一个简单的解决方案,就像Object.assigndeep 一样,适用于数组,无需任何修改。

function deepAssign(target, ...sources) {
  for (source of sources) {
    for (let k in source) {
      let vs = source[k], vt = target[k]
      if (Object(vs) == vs && Object(vt) === vt) {
        target[k] = deepAssign(vt, vs)
        continue
      }
      target[k] = source[k]
    }
  }
  return target
}

x = { a: { a: 1 }, b: [1,2] }
y = { a: { b: 1 }, b: [3] }
z = { c: 3, b: [,,,4] }
x = deepAssign(x, y, z)

console.log(JSON.stringify(x) === JSON.stringify({
  "a": {
    "a": 1,
    "b": 1
  },
  "b": [ 1, 2, null, 4 ],
  "c": 3
}))
Run Code Online (Sandbox Code Playgroud)


use*_*882 9

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


Vin*_*ent 9

很多答案使用了几十行代码,或者需要在项目中添加一个新的库,但是如果你使用递归,这只是4行代码。

function merge(current, updates) {
  for (key of Object.keys(updates)) {
    if (!current.hasOwnProperty(key) || typeof updates[key] !== 'object') current[key] = updates[key];
    else merge(current[key], updates[key]);
  }
  return current;
}
console.log(merge({ a: { a: 1 } }, { a: { b: 1 } }));
Run Code Online (Sandbox Code Playgroud)

数组处理:以上版本用新的数组覆盖旧的数组值。如果您希望它保留旧的数组值并添加新的值,只需else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])elsestatament上方添加一个块即可。

  • 我喜欢它,但它需要对“当前”进行简单的未定义检查,否则 {foo: undefined} 不会合并。只需在 for 循环之前添加一个 if(current) 即可。 (2认同)

am0*_*0wa 8

这是TypeScript实现:

export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T  => {
  if (!sources.length) {
    return target;
  }
  const source = sources.shift();
  if (source === undefined) {
    return target;
  }

  if (isMergebleObject(target) && isMergebleObject(source)) {
    Object.keys(source).forEach(function(key: string) {
      if (isMergebleObject(source[key])) {
        if (!target[key]) {
          target[key] = {};
        }
        mergeObjects(target[key], source[key]);
      } else {
        target[key] = source[key];
      }
    });
  }

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

const isObject = (item: any): boolean => {
  return item !== null && typeof item === 'object';
};

const isMergebleObject = (item): boolean => {
  return isObject(item) && !Array.isArray(item);
};
Run Code Online (Sandbox Code Playgroud)

和单元测试:

describe('merge', () => {
  it('should merge Objects and all nested Ones', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C', d: {} };
    const obj2 = { a: { a2: 'A2'}, b: { b1: 'B1'}, d: null };
    const obj3 = { a: { a1: 'A1', a2: 'A2'}, b: { b1: 'B1'}, c: 'C', d: null};
    expect(mergeObjects({}, obj1, obj2)).toEqual(obj3);
  });
  it('should behave like Object.assign on the top level', () => {
    const obj1 = { a: { a1: 'A1'}, c: 'C'};
    const obj2 = { a: undefined, b: { b1: 'B1'}};
    expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
  });
  it('should not merge array values, just override', () => {
    const obj1 = {a: ['A', 'B']};
    const obj2 = {a: ['C'], b: ['D']};
    expect(mergeObjects({}, obj1, obj2)).toEqual({a: ['C'], b: ['D']});
  });
  it('typed merge', () => {
    expect(mergeObjects<TestPosition>(new TestPosition(0, 0), new TestPosition(1, 1)))
      .toEqual(new TestPosition(1, 1));
  });
});

class TestPosition {
  constructor(public x: number = 0, public y: number = 0) {/*empty*/}
}
Run Code Online (Sandbox Code Playgroud)


Dim*_*iwa 7

如果您使用的是ImmutableJS,您可以使用mergeDeep:

fromJS(options).mergeDeep(options2).toJS();
Run Code Online (Sandbox Code Playgroud)

  • @EliseChant我不这么认为。你为什么不澄清? (2认同)

cur*_*all 7

我想提出一个非常简单的ES5替代方案。该函数获取2个参数- target并且source必须为“对象”类型。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;
}
Run Code Online (Sandbox Code Playgroud)

情况:

  • 如果target没有source财产,target得到它;
  • 如果target确实有一个source属性,并且targetsource都不是两个对象(4个案例中有3个),则target的属性会被覆盖;
  • 如果target确实具有一个source属性并且它们都是对象/数组(剩下1种情况),则合并两个对象(或两个数组的串联)时会发生递归;

还考虑以下几点

  1. 数组+ obj =数组
  2. obj +数组= obj
  3. obj + obj = obj(递归合并)
  4. 数组+数组=数组(concat)

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

看一个例子(如果需要,可以尝试一下)

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;
}
Run Code Online (Sandbox Code Playgroud)

有一个限制-浏览器的调用堆栈长度。现代浏览器会在某些真正深层次的递归(例如成千上万的嵌套调用)上引发错误。另外,您还可以通过添加新条件和类型检查来随意处理数组+对象等情况。


pra*_*mil 6

这是另一个ES6解决方案,适用于对象和数组.

function deepMerge(...sources) {
  let acc = {}
  for (const source of sources) {
    if (source instanceof Array) {
      if (!(acc instanceof Array)) {
        acc = []
      }
      acc = [...acc, ...source]
    } else if (source instanceof Object) {
      for (let [key, value] of Object.entries(source)) {
        if (value instanceof Object && key in acc) {
          value = deepMerge(acc[key], value)
        }
        acc = { ...acc, [key]: value }
      }
    }
  }
  return acc
}
Run Code Online (Sandbox Code Playgroud)

  • 是经过测试和/或属于库的一部分,看起来不错,但希望确保它已被证明是经过验证的。 (2认同)

Eze*_*iel 6

这里的大多数示例似乎太复杂了,我在我创建的 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;
};
Run Code Online (Sandbox Code Playgroud)

在普通 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
  };
};
Run Code Online (Sandbox Code Playgroud)

这是我的测试用例来展示如何使用它

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' } } });
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

如果您认为我缺少某些功能,请告诉我。


Abi*_*aya 5

我们可以使用$.extend(true,object1,object2)进行深度合并。值true表示递归合并两个对象,修改第一个。

$扩展(真,目标,对象)

  • 提问者从未表示他们正在使用 jquery,并且似乎在要求本机 javascript 解决方案。 (11认同)

sud*_* tk 5

以下函数对对象进行了深层复制,它涵盖了复制基元,数组以及对象的过程

 function mergeDeep (target, source)  {
    if (typeof target == "object" && typeof source == "object") {
        for (const key in source) {
            if (source[key] === null && (target[key] === undefined || target[key] === null)) {
                target[key] = null;
            } else if (source[key] instanceof Array) {
                if (!target[key]) target[key] = [];
                //concatenate arrays
                target[key] = target[key].concat(source[key]);
            } else if (typeof source[key] == "object") {
                if (!target[key]) target[key] = {};
                this.mergeDeep(target[key], source[key]);
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}
Run Code Online (Sandbox Code Playgroud)


y.c*_*y.c 5

使用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));
Run Code Online (Sandbox Code Playgroud)


rev*_*elt 5

有没有办法做到这一点?

如果可以将npm库用作解决方案,那么您真正使用的对象合并高级功能将允许您深度合并对象,并使用熟悉的回调函数自定义/覆盖每个合并操作。它的主要思想不只是深度合并-当两个键相同时,值会发生什么?这个库可以解决这个问题-当两个键发生冲突时,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
//      }
//    }
Run Code Online (Sandbox Code Playgroud)

该算法递归地遍历所有输入对象键,进行比较并生成并返回新的合并结果。


pri*_*lus 5

与减少

export const merge = (objFrom, objTo) => Object.keys(objFrom)
    .reduce(
        (merged, key) => {
            merged[key] = objFrom[key] instanceof Object && !Array.isArray(objFrom[key])
                ? merge(objFrom[key], merged[key] ?? {})
                : objFrom[key]
            return merged
        }, { ...objTo }
    )
Run Code Online (Sandbox Code Playgroud)
test('merge', async () => {
    const obj1 = { par1: -1, par2: { par2_1: -21, par2_5: -25 }, arr: [0,1,2] }
    const obj2 = { par1: 1, par2: { par2_1: 21 }, par3: 3, arr: [3,4,5] }
    const obj3 = merge3(obj1, obj2)
    expect(obj3).toEqual(
        { par1: -1, par2: { par2_1: -21, par2_5: -25 }, par3: 3, arr: [0,1,2] }
    )
})
Run Code Online (Sandbox Code Playgroud)