489

Object.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().

有没有办法做到这一点?

4

47 回答 47

230

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

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

请注意,这将导致循环引用的无限递归。如果您认为自己会遇到这个问题,这里有一些关于如何检测循环引用的很好的答案。

于 2016-01-12T17:15:35.143 回答
183

您可以使用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 }] }
于 2017-01-04T23:05:13.473 回答
120

当涉及到宿主对象或任何比一袋值更复杂的对象时,这个问题并不重要

  • 您是调用 getter 来获取值还是复制属性描述符?
  • 如果合并目标有一个设置器(自己的属性或在其原型链中)怎么办?您是否认为该值已经存在或调用 setter 来更新当前值?
  • 你是调用自己的属性函数还是复制它们?如果它们是绑定函数或箭头函数,这取决于定义它们时作用域链中的某些内容怎么办?
  • 如果它类似于 DOM 节点怎么办?您当然不想将其视为简单对象,而只是将其所有属性深度合并到
  • 如何处理像数组、映射或集合这样的“简单”结构?考虑它们已经存在还是合并它们?
  • 如何处理不可枚举的自身属性?
  • 新的子树呢?简单地通过引用或深度克隆分配?
  • 如何处理冻结/密封/不可扩展的对象?

要记住的另一件事:包含循环的对象图。这通常不难处理——只需保留一个Set已经访问过的源对象——但经常被遗忘。

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

换句话说,没有一刀切的算法,您要么必须自己动手,要么寻找恰好涵盖您的用例的库方法。

于 2015-01-31T05:35:26.163 回答
88

这是@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;
}
于 2016-05-11T13:43:24.797 回答
75

由于这个问题仍然存在,这里有另一种方法:

  • 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);

于 2018-01-12T01:15:47.347 回答
45

我知道已经有很多答案,并且有很多评论认为它们不起作用。唯一的共识是它太复杂了,没有人为它制定标准。但是,SO 中大多数公认的答案都暴露了广泛使用的“简单技巧”。因此,对于像我这样不是专家但想通过更多地了解 javascript 的复杂性来编写更安全的代码的所有人,我将尝试阐明一些情况。

在弄脏手之前,让我澄清两点:

  • [免责声明] 我在下面提出了一个函数,它解决了我们如何深入循环javascript 对象中进行复制,并说明了通常过于简短的评论。它不是生产就绪的。为了清楚起见,我特意搁置了其他考虑因素,例如循环对象(通过一组或不冲突的符号属性跟踪)、复制参考值或深度克隆、不可变目标对象(再次深度克隆?)、逐个案例研究每种类型的对象,通过访问器获取/设置属性……此外,我没有测试性能——尽管它很重要——因为这也不是重点。
  • 我将使用copyassign terms 而不是merge。因为在我看来,合并是保守的,应该会因冲突而失败。在这里,当发生冲突时,我们希望源覆盖目标。喜欢Object.assign

for..in具有或Object.keys具有误导性的答案

制作深拷贝似乎是如此基本和普遍的做法,以至于我们期望找到一个单行,或者至少,通过简单的递归快速获胜。我们不希望我们需要一个库或编写一个 100 行的自定义函数。

当我第一次阅读Salakar 的答案时,我真的认为我可以做得更好更简单(您可以将其与Object.assignon进行比较x={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!

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

for..in将在您不想要(或不知道)的情况下提供源、其原型和完整原型链的属性。您的目标最终可能会包含太多属性,混淆了原型属性和自己的属性。

如果您正在编写一个通用函数并且您没有使用Object.getOwnPropertyDescriptors, Object.getOwnPropertyNames, Object.getOwnPropertySymbolsor Object.getPrototypeOf,那么您很可能做错了。

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

首先,确保您了解 Javascript 对象是什么。在 Javascript 中,一个对象由它自己的属性和一个(父)原型对象组成。原型对象又由它自己的属性和一个原型对象组成。依此类推,定义原型链。

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

最后,对象的类型很多。您可能希望以不同的方式处理对象 Object 与对象 Date 或对象 Function。

因此,在编写深层副本时,您至少应该回答以下问题:

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

对于我的示例,我认为只有object Objects 是deep,因为其他构造函数创建的其他对象可能不适合深入查看。从此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);
}

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

const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};

提议的功能

你可以在这个 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;
    }
}

可以这样使用:

const x = { a: { a: 1 } },
      y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }
于 2018-02-02T09:27:30.250 回答
27

如果你想拥有一个单行而不需要像 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 }

这适用于复杂的对象和数组。这是一个真正的全能解决方案。

于 2019-08-02T18:27:16.457 回答
15

我使用 lodash:

import _ = require('lodash');
value = _.merge(value1, value2);
于 2019-01-23T11:14:16.567 回答
13

这是 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);
};

和单元测试:

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*/}
}
于 2017-10-27T10:47:40.503 回答
12

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

于 2018-08-10T13:53:41.590 回答
12

很多答案使用了几十行代码,或者需要在项目中添加一个新的库,但如果你使用递归,这只是 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 } }));

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

于 2019-11-23T14:07:25.407 回答
11

在这里,直截了当;

一个简单的解决方案,就像Object.assign深度一样工作,并且适用于数组,无需任何修改。

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

于 2020-04-23T19:02:59.203 回答
10

我想介绍一个非常简单的 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;
}

案例:

  • 如果target没有source属性,target获取它;
  • 如果target确实有一个source属性并且target&source不是两个对象(4 种情况下的 3 种情况),则target' 的属性将被覆盖;
  • 如果target确实有一个source属性并且它们都是对象/数组(剩下的一种情况),那么递归会合并两个对象(或两个数组的串联);

还要考虑以下几点

  1. 数组 + obj = 数组
  2. 对象 + 数组 = 对象
  3. obj + obj = obj(递归合并)
  4. 数组 + 数组 = 数组(连续)

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

看一个例子(如果你愿意,可以玩它)

var a = {
   "a_prop": 1,
   "arr_prop": [4, 5, 6],
   "obj": {
     "a_prop": {
       "t_prop": 'test'
     },
     "b_prop": 2
   }
};

var b = {
   "a_prop": 5,
   "arr_prop": [7, 8, 9],
   "b_prop": 15,
   "obj": {
     "a_prop": {
       "u_prop": false
     },
     "b_prop": {
        "s_prop": null
     }
   }
};

function deepMerge(target, source) {
    if(typeof target !== 'object' || typeof source !== 'object') return false;
    for(var prop in source) {
    if(!source.hasOwnProperty(prop)) continue;
      if(prop in target) {
        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) {
              target[prop] = target[prop].concat(source[prop]);
            } else {
              target[prop] = deepMerge(target[prop], source[prop]); 
            } 
          }  
        }
      } else {
        target[prop] = source[prop]; 
      }
    }
  return target;
}

console.log(deepMerge(a, b));

有一个限制 - 浏览器的调用堆栈长度。现代浏览器会在某个非常深的递归级别抛出错误(想想成千上万的嵌套调用)。此外,您还可以通过添加新条件和类型检查来随意处理数组 + 对象等情况。

于 2018-05-08T22:04:58.103 回答
7

使用 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));

于 2018-05-08T07:25:28.093 回答
7

有没有办法做到这一点?

如果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
//      }
//    }

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

于 2018-07-03T07:33:50.347 回答
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;
};

在普通 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' } } });
    });
  });
});

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

于 2018-10-31T21:49:49.343 回答
6

以下函数对对象进行深度复制,它涵盖了复制原语、数组以及对象

 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;
}
于 2018-01-16T07:22:15.523 回答
5

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

$extend(true,目标,对象)

于 2017-02-21T04:45:17.840 回答
5

如果您使用ImmutableJS,您可以使用mergeDeep

fromJS(options).mergeDeep(options2).toJS();
于 2017-01-23T11:26:38.473 回答
4

Ramda 是一个很好的 javascript 函数库,具有 mergeDeepLeft 和 mergeDeepRight。这些中的任何一个都可以很好地解决这个问题。请在此处查看文档:https ://ramdajs.com/docs/#mergeDeepLeft

对于有问题的具体示例,我们可以使用:

import { mergeDeepLeft } from 'ramda'
const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
const z = mergeDeepLeft(x, y)) // {"a":{"a":1,"b":1}}
于 2019-04-18T01:24:28.737 回答
4

减少

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 }
    )
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] }
    )
})
于 2021-04-25T09:11:19.837 回答
3

我在加载缓存的 redux 状态时遇到了这个问题。如果我只是加载缓存的状态,我会在更新状态结构的新应用程序版本中遇到错误。

已经提到过,lodash 提供了merge我使用的功能:

const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);
于 2018-01-10T10:57:21.557 回答
2
// copies all properties from source object to dest object recursively
export function recursivelyMoveProperties(source, dest) {
  for (const prop in source) {
    if (!source.hasOwnProperty(prop)) {
      continue;
    }

    if (source[prop] === null) {
      // property is null
      dest[prop] = source[prop];
      continue;
    }

    if (typeof source[prop] === 'object') {
      // if property is object let's dive into in
      if (Array.isArray(source[prop])) {
        dest[prop] = [];
      } else {
        if (!dest.hasOwnProperty(prop)
        || typeof dest[prop] !== 'object'
        || dest[prop] === null || Array.isArray(dest[prop])
        || !Object.keys(dest[prop]).length) {
          dest[prop] = {};
        }
      }
      recursivelyMoveProperties(source[prop], dest[prop]);
      continue;
    }

    // property is simple type: string, number, e.t.c
    dest[prop] = source[prop];
  }
  return dest;
}

单元测试:

describe('recursivelyMoveProperties', () => {
    it('should copy properties correctly', () => {
      const source: any = {
        propS1: 'str1',
        propS2: 'str2',
        propN1: 1,
        propN2: 2,
        propA1: [1, 2, 3],
        propA2: [],
        propB1: true,
        propB2: false,
        propU1: null,
        propU2: null,
        propD1: undefined,
        propD2: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subN1: 21,
          subN2: 22,
          subA1: [21, 22, 23],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      let dest: any = {
        propS2: 'str2',
        propS3: 'str3',
        propN2: -2,
        propN3: 3,
        propA2: [2, 2],
        propA3: [3, 2, 1],
        propB2: true,
        propB3: false,
        propU2: 'not null',
        propU3: null,
        propD2: 'defined',
        propD3: undefined,
        propO2: {
          subS2: 'inv22',
          subS3: 'sub23',
          subN2: -22,
          subN3: 23,
          subA2: [5, 5, 5],
          subA3: [31, 32, 33],
          subB2: false,
          subB3: true,
          subU2: 'not null --- ',
          subU3: null,
          subD2: ' not undefined ----',
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      };
      dest = recursivelyMoveProperties(source, dest);

      expect(dest).toEqual({
        propS1: 'str1',
        propS2: 'str2',
        propS3: 'str3',
        propN1: 1,
        propN2: 2,
        propN3: 3,
        propA1: [1, 2, 3],
        propA2: [],
        propA3: [3, 2, 1],
        propB1: true,
        propB2: false,
        propB3: false,
        propU1: null,
        propU2: null,
        propU3: null,
        propD1: undefined,
        propD2: undefined,
        propD3: undefined,
        propO1: {
          subS1: 'sub11',
          subS2: 'sub12',
          subN1: 11,
          subN2: 12,
          subA1: [11, 12, 13],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
        propO2: {
          subS1: 'sub21',
          subS2: 'sub22',
          subS3: 'sub23',
          subN1: 21,
          subN2: 22,
          subN3: 23,
          subA1: [21, 22, 23],
          subA2: [],
          subA3: [31, 32, 33],
          subB1: false,
          subB2: true,
          subB3: true,
          subU1: null,
          subU2: null,
          subU3: null,
          subD1: undefined,
          subD2: undefined,
          subD3: undefined,
        },
        propO3: {
          subS1: 'sub31',
          subS2: 'sub32',
          subN1: 31,
          subN2: 32,
          subA1: [31, 32, 33],
          subA2: [],
          subB1: false,
          subB2: true,
          subU1: null,
          subU2: null,
          subD1: undefined,
          subD2: undefined,
        },
      });
    });
  });
于 2019-04-11T13:30:12.483 回答
2

用例:合并默认配置

如果我们以以下形式定义配置:

const defaultConf = {
    prop1: 'config1',
    prop2: 'config2'
}

我们可以通过以下方式定义更具体的配置:

const moreSpecificConf = {
    ...defaultConf,
    prop3: 'config3'
}

但是如果这些配置包含嵌套结构,这种方法就不再适用了。

因此,我编写了一个函数,它仅在某种意义上合并对象{ key: value, ... }并替换其余对象。

const isObject = (val) => val === Object(val);

const merge = (...objects) =>
    objects.reduce(
        (obj1, obj2) => ({
            ...obj1,
            ...obj2,
            ...Object.keys(obj2)
                .filter((key) => key in obj1 && isObject(obj1[key]) && isObject(obj2[key]))
                .map((key) => ({[key]: merge(obj1[key], obj2[key])}))
                .reduce((n1, n2) => ({...n1, ...n2}), {})
        }),
        {}
    );
于 2019-11-12T12:04:48.740 回答
2

使用这个功能:

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;
    }
于 2019-02-27T11:31:40.477 回答
2

这是我刚刚写的另一个支持数组的。它连接它们。

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


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(Array.isArray(target)) {
        if(Array.isArray(source)) {
            target.push(...source);
        } else {
            target.push(source);
        }
    } else if(isPlainObject(target)) {
        if(isPlainObject(source)) {
            for(let key of Object.keys(source)) {
                if(!target[key]) {
                    target[key] = source[key];
                } else {
                    mergeDeep(target[key], source[key]);
                }
            }
        } else {
            throw new Error(`Cannot merge object with non-object`);
        }
    } else {
        target = source;
    }

    return mergeDeep(target, ...sources);
};
于 2017-05-30T19:11:09.257 回答
2

使用递归的另一种变体,希望对您有用。

const merge = (obj1, obj2) => {

    const recursiveMerge = (obj, entries) => {
         for (const [key, value] of entries) {
            if (typeof value === "object") {
               obj[key] = obj[key] ? {...obj[key]} : {};
               recursiveMerge(obj[key], Object.entries(value))
            else {
               obj[key] = value;
            }
          }

          return obj;
    }

    return recursiveMerge(obj1, Object.entries(obj2))
}
于 2020-11-25T09:02:40.773 回答
2

这是一个廉价的深度合并,它使用了我能想到的尽可能少的代码。每个源在存在时都会覆盖先前的属性。

const { keys } = Object;

const isObject = a => typeof a === "object" && !Array.isArray(a);
const merge = (a, b) =>
  isObject(a) && isObject(b)
    ? deepMerge(a, b)
    : isObject(a) && !isObject(b)
    ? a
    : b;

const coalesceByKey = source => (acc, key) =>
  (acc[key] && source[key]
    ? (acc[key] = merge(acc[key], source[key]))
    : (acc[key] = source[key])) && acc;

/**
 * Merge all sources into the target
 * overwriting primitive values in the the accumulated target as we go (if they already exist)
 * @param {*} target
 * @param  {...any} sources
 */
const deepMerge = (target, ...sources) =>
  sources.reduce(
    (acc, source) => keys(source).reduce(coalesceByKey(source), acc),
    target
  );

console.log(deepMerge({ a: 1 }, { a: 2 }));
console.log(deepMerge({ a: 1 }, { a: { b: 2 } }));
console.log(deepMerge({ a: { b: 2 } }, { a: 1 }));
于 2019-04-17T22:24:48.953 回答
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);
  });
});

于 2020-11-28T13:48:27.113 回答
1

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

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

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

于 2017-05-28T01:36:59.723 回答
1

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

于 2018-03-10T03:55:47.973 回答
1

我发现只有 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' } }

于 2019-12-11T09:04:27.820 回答
1

我不喜欢任何现有的解决方案。所以,我继续写我自己的。

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

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

谢谢

于 2021-09-03T05:56:19.873 回答
1

我正在使用以下简短函数来深度合并对象。
这对我很有效。
作者在这里完全解释了它是如何工作的。

/*!
 * Merge two or more objects together.
 * (c) 2017 Chris Ferdinandi, MIT License, https://gomakethings.com
 * @param   {Boolean}  deep     If true, do a deep (or recursive) merge [optional]
 * @param   {Object}   objects  The objects to merge together
 * @returns {Object}            Merged values of defaults and options
 * 
 * Use the function as follows:
 * let shallowMerge = extend(obj1, obj2);
 * let deepMerge = extend(true, obj1, obj2)
 */

var extend = function () {

    // Variables
    var extended = {};
    var deep = false;
    var i = 0;

    // Check if a deep merge
    if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) {
        deep = arguments[0];
        i++;
    }

    // Merge the object into the extended object
    var merge = function (obj) {
        for (var prop in obj) {
            if (obj.hasOwnProperty(prop)) {
                // If property is an object, merge properties
                if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {
                    extended[prop] = extend(extended[prop], obj[prop]);
                } else {
                    extended[prop] = obj[prop];
                }
            }
        }
    };

    // Loop through each object and conduct a merge
    for (; i < arguments.length; i++) {
        merge(arguments[i]);
    }

    return extended;

};
于 2019-12-08T19:28:55.373 回答
1

https://lodash.com/docs/4.17.15#defaultsDeep

注意:此方法会改变源。

_.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
// => { 'a': { 'b': 2, 'c': 3 } }
于 2020-09-25T03:08:38.220 回答
0

我试图写一个Object.assignDeep基于 on mdn 的Object.assignpollyfill

(ES5)

Object.assignDeep = function (target, varArgs) { // .length of function is 2
    'use strict';
    if (target == null) { // TypeError if undefined or null
        throw new TypeError('Cannot convert undefined or null to object');
    }

    var to = Object(target);

    for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) { // Skip over if undefined or null
            for (var nextKey in nextSource) {
                // Avoid bugs when hasOwnProperty is shadowed
                if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                    if (typeof to[nextKey] === 'object' 
                        && to[nextKey] 
                        && typeof nextSource[nextKey] === 'object' 
                        && nextSource[nextKey]) {                        
                        Object.assignDeep(to[nextKey], nextSource[nextKey]);
                    } else {
                        to[nextKey] = nextSource[nextKey];
                    }
                }
            }
        }
    }
    return to;
};
console.log(Object.assignDeep({},{a:{b:{c:1,d:1}}},{a:{b:{c:2,e:2}}}))

于 2017-03-07T11:01:10.773 回答
0

有一个 lodash 包专门处理深度克隆对象。优点是您不必包含整个 lodash 库。

它叫lodash.clonedeep

在nodejs中用法是这样的

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

在 ReactJS 中,用法是

import cloneDeep from 'lodash/cloneDeep';

const newObject = cloneDeep(oldObject);

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

于 2020-07-21T04:49:05.563 回答
0

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

Object.assign 文档表明它不会进行深度克隆。

于 2016-08-11T23:38:38.303 回答
0

如果要合并多个普通对象(不要修改输入对象)。基于Object.assign polyfill

function isPlainObject(a) {
    return (!!a) && (a.constructor === Object);
}

function merge(target) {
    let to = Object.assign({}, target);

    for (let index = 1; index < arguments.length; index++) {
        let nextSource = arguments[index];

        if (nextSource !== null && nextSource !== undefined) {
            for (let nextKey in nextSource) {
                // Avoid bugs when hasOwnProperty is shadowed
                if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                    if (isPlainObject(to[nextKey]) && isPlainObject(nextSource[nextKey])) {
                        to[nextKey] = merge(to[nextKey], nextSource[nextKey]);
                    } else {
                        to[nextKey] = nextSource[nextKey];
                    }
                }
            }
        }
    }

    return to;
}

// Usage

var obj1 = {
    a: 1,
    b: {
        x: 2,
        y: {
            t: 3,
            u: 4
        }
    },
    c: "hi"
};

var obj2 = {
    b: {
        x: 200,
        y: {
            u: 4000,
            v: 5000
        }
    }
};

var obj3 = {
    c: "hello"
};

console.log("result", merge(obj1, obj2, obj3));
console.log("obj1", obj1);
console.log("obj2", obj2);
console.log("obj3", obj3);

如果你想以有限的深度合并

function isPlainObject(a) {
        return (!!a) && (a.constructor === Object);
    }

function merge(target) {
let to = Object.assign({}, target);

const hasDepth = arguments.length > 2 && typeof arguments[arguments.length - 1] === 'number';

const depth = hasDepth ? arguments[arguments.length - 1] : Infinity;

const lastObjectIndex = hasDepth ? arguments.length - 2 : arguments.length - 1;

for (let index = 1; index <= lastObjectIndex; index++) {
    let nextSource = arguments[index];

    if (nextSource !== null && nextSource !== undefined) {
        for (let nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                if (depth > 0 && isPlainObject(to[nextKey]) && isPlainObject(nextSource[nextKey])) {
                    to[nextKey] = merge(to[nextKey], nextSource[nextKey], depth - 1);
                } else {
                    to[nextKey] = nextSource[nextKey];
                }
            }
        }
    }
}

return to;
}

// Usage

var obj1 = {
    a: 1,
    b: {
        x: 2,
        y: {
            t: 3,
            u: 4,
            z: {zzz: 100}
        }
    },
    c: "hi"
};

var obj2 = {
    b: {
        y: {
            u: 4000,
            v: 5000,
            z: {}
        }
    }
};

var obj3 = {
    c: "hello"
};

console.log('deep 0', merge(obj1, obj2, obj3, 0));
console.log('deep 1', merge(obj1, obj2, obj3, 1));
console.log('deep 2', merge(obj1, obj2, obj3, 2));
console.log('deep 2', merge(obj1, obj2, obj3, 4));

于 2021-08-15T07:09:20.373 回答
0

简单的递归解决方案

使用Object.entries, 迭代其中一个对象。如果条目不存在则添加条目,如果条目是对象则递归。

const x = { a: { a: 1 } }
const y = { a: { b: 1 } }

const z = 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[key], secondObj[key])
      }
    })

}
mergeIntoZ(x, z)
console.log(z)

于 2021-08-24T20:28:43.903 回答
0

在此处使用实用程序“deepmerge”(链接)或此代码:LINK,它工作正常

于 2020-05-01T09:24:34.767 回答
0
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]
于 2017-07-20T08:32:30.477 回答
0

我已经阅读了这里的所有答案,并拼凑了我自己的一个。大多数现有答案都没有按我想要的方式工作。

这对于 2021 年来说是非常可怕的,所以任何改进的技巧,我都听好了!

这是在打字稿中

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
}

平面数组使用删除重复值[...new Set(...)]

嵌套数组使用concat.

于 2021-12-22T16:38:01.560 回答
0

适用于对象和数组的 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
  }
}
于 2022-02-19T17:26:03.320 回答
-1

我使用 es6 将此方法用于深度分配。

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

function deepAssign(...objs) {
    if (objs.length < 2) {
        throw new Error('Need two or more objects to merge')
    }

    const target = objs[0]
    for (let i = 1; i < objs.length; i++) {
        const source = objs[i]
        Object.keys(source).forEach(prop => {
            const value = source[prop]
            if (isObject(value)) {
                if (target.hasOwnProperty(prop) && isObject(target[prop])) {
                    target[prop] = deepAssign(target[prop], value)
                } else {
                    target[prop] = value
                }
            } else if (Array.isArray(value)) {
                if (target.hasOwnProperty(prop) && Array.isArray(target[prop])) {
                    const targetArray = target[prop]
                    value.forEach((sourceItem, itemIndex) => {
                        if (itemIndex < targetArray.length) {
                            const targetItem = targetArray[itemIndex]

                            if (Object.is(targetItem, sourceItem)) {
                                return
                            }

                            if (isObject(targetItem) && isObject(sourceItem)) {
                                targetArray[itemIndex] = deepAssign(targetItem, sourceItem)
                            } else if (Array.isArray(targetItem) && Array.isArray(sourceItem)) {
                                targetArray[itemIndex] = deepAssign(targetItem, sourceItem)
                            } else {
                                targetArray[itemIndex] = sourceItem
                            }
                        } else {
                            targetArray.push(sourceItem)
                        }
                    })
                } else {
                    target[prop] = value
                }
            } else {
                target[prop] = value
            }
        })
    }

    return target
}
于 2016-08-08T05:47:05.200 回答
-2

它不存在,但您可以使用JSON.parse(JSON.stringify(jobs))

于 2017-03-06T12:55:29.023 回答
-4

这很简单并且有效:

let item = {
    firstName: 'Jonnie',
    lastName: 'Walker',
    fullName: function fullName() {
            return 'Jonnie Walker';
    }
Object.assign(Object.create(item), item);

解释:

Object.create()创建新对象。如果您将参数传递给函数,它将为您创建具有其他对象原型的对象。因此,如果您在对象原型上有任何函数,它们将被传递给其他对象的原型。

Object.assign()合并两个对象并创建全新的对象,它们不再有引用。所以这个例子对我很有用。

于 2016-12-02T11:26:49.663 回答