如何用深度合并代替浅合并?

Object.assign对象传播都只做浅合并。

这个问题的一个例子:

// 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()也是如此。

有办法做到这一点吗?

325723 次浏览

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

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

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

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

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

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

你将在下面的答案中找到一个不可更改的版本。

这里有一些关于如何检测循环引用的很好的答案,如果你认为你会面临这个问题。

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

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

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

对象。指定文档建议它不做深度克隆。

这很简单,也很有效:

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

解释:

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

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

你可以使用Lodash合并:

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


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


console.log(_.merge(object, other));
// => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>

如果你正在使用ImmutableJS,你可以使用mergeDeep:

fromJS(options).mergeDeep(options2).toJS();

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

$extend(true,target,object)

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

我试着写一个Object.assignDeep,这是基于Object.assign中数的填充物。

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

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

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

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

这是我刚刚写的另一个支持数组的程序。它把它们连接起来。

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

下面是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*/}
}

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

前面已经提到过,lodash提供了merge函数,我使用了这个函数:

const currentInitialState = configureState().getState();
const mergedState = _.merge({}, currentInitialState, cachedState);
const store = configureState(mergedState);

2022年更新:

我创建了mergician来满足评论中讨论的各种合并/克隆需求。它基于与我最初的答案相同的概念(如下),但提供了可配置的选项:

与本机方法和其他合并/克隆实用程序不同,Mergician提供了定制合并/克隆过程的高级选项。这些选项使检查、过滤和修改键和属性变得容易;合并或跳过唯一键、通用键和通用键(即交叉键、并和差键);以及从数组中归并、排序和删除重复项。属性访问器和描述符也被正确处理,确保getter/setter函数被保留,描述符值被定义在新的合并/克隆对象上。

值得注意的是,mergician比lodash等类似工具要小得多(1.5k min+gzip)。合并(5.1k min+gzip)。

  • GitHub: # EYZ0
  • NPM: # EYZ0
  • 文档:# EYZ0

最初的回答:

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

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

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

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

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

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

  • [免责声明]我提出了下面的一个函数,解决了我们如何将深循环转换为javascript对象进行复制,并说明了通常太简短的注释。它还不能用于生产。为了清晰起见,我故意将其他考虑因素放在一边,如圆形对象(通过一组或不冲突的符号属性跟踪),复制引用值或深克隆,不可变的目标对象(再次深度克隆?),每种类型的对象的个案研究,通过访问器获取/设置属性……此外,我没有测试性能——尽管这很重要——因为这也不是这里的重点。
  • 我将使用复制分配术语而不是合并。因为在我看来,合并是保守的,在冲突中应该失败。在这里,当发生冲突时,我们希望源覆盖目标。就像Object.assign一样。

带有for..inObject.keys的答案会误导人

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

当我第一次读Salakar的回答时,我真的认为我可以做得更好更简单(你可以将它与x={a:1}, y={a:{b:1}}上的Object.assign进行比较)。然后我读了the8472的回答,我想…没有那么容易的脱身,改进已经给出的答案不会让我们走得太远。

让我们暂时把深度复制和递归放在一边。只要考虑一下人们是如何(错误地)解析属性来复制一个非常简单的对象。

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.getOwnPropertyDescriptorsObject.getOwnPropertyNamesObject.getOwnPropertySymbolsObject.getPrototypeOf,那么你很可能做错了。

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

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

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

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

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

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

对于我的例子,我认为只有# eyz0是,因为其他构造函数创建的其他对象可能不适合深入研究。自定义这样

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

提出了功能

您可以在这恰好中测试它。

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

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

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

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

# EYZ0:

  1. Array + obj = Array
  2. Obj + array = Obj
  3. Obj + Obj = Obj(递归合并)
  4. Array + Array = Array (concat)

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

# EYZ0:

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

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

有办法做到这一点吗?

如果npm库可以用作解决方案,那么object-merge-advanced确实允许深度合并对象,并使用熟悉的回调函数自定义/覆盖每一个合并操作。它的主要思想不仅仅是深度合并——当两个键都是相同的时,值会发生什么变化?这个库负责处理这个问题——当两个键冲突时,object-merge-advanced会权衡类型,目的是在合并后保留尽可能多的数据:

object key merged weighting key value types to retain as much data as possible

第一个输入参数的键标记为#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
//      }
//    }

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

deepmerge npm包似乎是解决这个问题使用最广泛的库: # EYZ0 < / p >

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

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

我使用lodash:

import _ = require('lodash');
value = _.merge(value1, value2);

使用这个函数:

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;
}
// 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,
},
});
});
});

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

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

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

如果你想在不需要像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 }

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

用例:合并默认配置

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

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}), {})
}),
{}
);

许多答案使用数十行代码,或者需要向项目添加一个新库,但如果您使用递归,这只是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语句上方添加else if (current[key] instanceof Array && updates[key] instanceof Array) current[key] = current[key].concat(updates[key])块,你就都设置好了。

我使用下面的短函数进行深度合并对象
# EYZ0 < / p >

/*!
* 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;


};

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

这里,直走;

一个简单的解决方案,就像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
}))

< p >编辑: 我在别的地方回答过一种深度比较两个对象的新方法。 该方法也可以用于深度合并。如果你想要植入,请留言 # EYZ0 < / p >

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

它叫lodash.clonedeep

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

var cloneDeep = require('lodash.clonedeep');
 

const newObject = cloneDeep(oldObject);

在ReactJS中,用法是

import cloneDeep from 'lodash/cloneDeep';


const newObject = cloneDeep(oldObject);

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

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

注意:此方法会使源发生突变。

_.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
// => { 'a': { 'b': 2, 'c': 3 } }

另一个使用递归的变体,希望你觉得有用。

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

我的用例是将默认值合并到配置中。如果我的组件接受一个具有深度嵌套结构的配置对象,并且我的组件定义了默认配置,那么我希望在配置中为未提供的所有配置选项设置默认值。

使用示例:

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


与减少

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

如果您想合并多个普通对象(不要修改输入对象)。基于对象。分配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));

简单递归解

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

我不喜欢现有的解决方案。所以,我开始写我自己的。

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年来说是相当可怕的,所以任何改善的建议,我都洗耳恭听!

这是在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
}

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

嵌套数组使用concat连接。

适用于对象和数组的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
}
}

(本机解决方案)如果你知道你想要深度合并的属性,那么

const x = { a: { a: 1 } }
const y = { a: { b: 1 } }
Object.assign(y.a, x.a);
Object.assign(x, y);
// output: a: {b: 1, a: 1}