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

有办法做到这一点吗?


当前回答

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

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

其他回答

这很简单,也很有效:

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

解释:

create()创建新对象。如果你传递参数给函数,它将创建你的对象与其他对象的原型。如果你在一个对象的原型上有任何函数它们会被传递给另一个对象的原型。

object .assign()合并两个对象并创建一个全新的对象,它们不再有引用。这个例子对我来说很好。

当涉及到宿主对象或比值包更复杂的任何类型的对象时,这个问题就不那么简单了

do you invoke a getter to obtain a value or do you copy over the property descriptor? what if the merge target has a setter (either own property or in its prototype chain)? Do you consider the value as already-present or call the setter to update the current value? do you invoke own-property functions or copy them over? What if they're bound functions or arrow functions depending on something in their scope chain at the time they were defined? what if it's something like a DOM node? You certainly don't want to treat it as simple object and just deep-merge all its properties over into how to deal with "simple" structures like arrays or maps or sets? Consider them already-present or merge them too? how to deal with non-enumerable own properties? what about new subtrees? Simply assign by reference or deep clone? how to deal with frozen/sealed/non-extensible objects?

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

您可能应该编写一个深度合并函数,它只期望原始值和简单对象(结构化克隆算法最多可以处理的那些类型)作为合并源。如果遇到它不能处理的东西,或者只是通过引用而不是深度合并进行赋值,则抛出。

换句话说,没有一种适合所有人的算法,您要么必须使用自己的算法,要么寻找恰好涵盖您的用例的库方法。

这里,直走;

一个简单的解决方案,工作像Object。仅赋值deep,适用于数组,无需任何修改。

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

编辑: 我在别的地方回答过一种深度比较两个对象的新方法。 该方法也可以用于深度合并。如果你想要植入,请留言 https://stackoverflow.com/a/71177790/1919821

我想介绍一个相当简单的ES5替代方案。该函数获得2个参数——目标和源,必须为“object”类型。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获取source属性; 如果目标有source属性,而target & source没有 两个对象(4个中的3个),目标的属性被覆盖; 如果target确实有一个source属性,并且它们都是对象/数组(剩余1种情况),那么递归发生合并两个对象(或两个数组的连接);

还要考虑以下几点:

Array + obj = Array Obj + array = Obj Obj + Obj = Obj(递归合并) Array + Array = Array (concat)

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

看一个例子(如果你想的话,也可以玩一下):

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

有一个限制-浏览器的调用堆栈长度。现代浏览器会在一些真正深层的递归中抛出错误(想想成千上万的嵌套调用)。此外,您还可以自由地处理像数组+对象等情况,因为您希望添加新的条件和类型检查。

我知道已经有很多答案,也有很多评论认为它们行不通。唯一的共识是,它太复杂了,没有人为它制定标准。然而,SO中大多数公认的答案都暴露了被广泛使用的“简单技巧”。因此,对于像我这样不是专家,但希望通过掌握javascript的复杂性来编写更安全的代码的所有人,我将尝试提供一些启发。

在开始之前,让我先澄清两点:

[DISCLAIMER] I propose a function below that tackles how we deep loop into javascript objects for copy and illustrates what is generally too shortly commented. It is not production-ready. For sake of clarity, I have purposedly left aside other considerations like circular objects (track by a set or unconflicting symbol property), copying reference value or deep clone, immutable destination object (deep clone again?), case-by-case study of each type of objects, get/set properties via accessors... Also, I did not test performance -although it's important- because it's not the point here either. I'll use copy or assign terms instead of merge. Because in my mind a merge is conservative and should fail upon conflicts. Here, when conflicting, we want the source to overwrite the destination. Like Object.assign does.

用for. in或Object回答。钥匙容易误导人

创建深度复制似乎是非常基本和常见的实践,我们希望通过简单的递归找到一行代码,或者至少是快速获胜。我们不期望我们需要一个库或编写一个100行的自定义函数。

当我第一次读Salakar的回答时,我真的认为我可以做得更好,更简单(你可以把它与Object进行比较。赋值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!

对象。键将忽略自己的不可枚举属性,自己的符号键属性和所有原型的属性。如果你的对象没有这些也没关系。但要记住那个对象。为句柄分配自己的符号键可枚举属性。所以你的自定义拷贝失去了它的光彩。

in将在你不需要(或不知道)的情况下提供源代码、原型和完整原型链的属性。你的目标可能会有太多的属性,混淆了原型属性和自己的属性。

如果你在写一个通用函数而你没有使用Object。getOwnPropertyDescriptors,对象。getOwnPropertyNames,对象。getOwnPropertySymbols或Object。getPrototypeOf,你很可能做错了。

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

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

属性是一对键(字符串或符号)和描述符(值或get/set访问器,以及像enumerable这样的属性)。

最后,还有许多类型的对象。您可能希望以不同的方式处理对象(object)与对象(Date)或对象(Function)。

因此,在撰写深度文案时,你至少应该回答以下问题:

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

对于我的例子,我认为只有对象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);
}

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

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

提出了功能

你可以用这个活塞测试。

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