合并两个对象而不覆盖

Posted

技术标签:

【中文标题】合并两个对象而不覆盖【英文标题】:Merge two objects without override 【发布时间】:2014-01-02 14:51:51 【问题描述】:

我有一个这样的默认对象:

var default = 
    abc: "123",
    def: "456",
    ghi: 
       jkl: "789",
       mno: "012"
    
;

我还有一个喜欢的:

var values = 
    abc: "zzz",
    ghi: 
       jkl: "yyy",
    
;

如何将这 2 个对象与以下结果合并(不覆盖)?

var values = 
    abc: "zzz",
    def: "456",
    ghi: 
       jkl: "yyy",
       mno: "012"
    
;

(我不想更改默认对象!)

【问题讨论】:

【参考方案1】:

对于那些不使用 jQuery 的人来说,这里有一个 vanilla-js 解决方案。

解决方案

function extend (target) 
    for(var i=1; i<arguments.length; ++i) 
        var from = arguments[i];
        if(typeof from !== 'object') continue;
        for(var j in from) 
            if(from.hasOwnProperty(j)) 
                target[j] = typeof from[j]==='object'
                ? extend(, target[j], from[j])
                : from[j];
            
        
    
    return target;

压缩(使用Closure Compiler):

只有 199 个字符!

var extend=function e(c)for(var d=1;d<arguments.length;++d)var a=arguments[d];if("object"===typeof a)for(var b in a)a.hasOwnProperty(b)&&(c[b]="object"===typeof a[b]?e(,c[b],a[b]):a[b])return c

如何使用

extend(target, obj1, obj2); // returns target

如果你只想合并,使用

var merged = extend(, obj1, obj2);

特点

它不查看对象的原型。 忽略非对象。 递归是为了合并对象属性。 target 的属性中引用的对象,如果扩展,将被新对象替换,而原始对象不会被修改。 如果属性名称相同,合并值将是最后一个(按参数顺序)非对象值之后的对象的合并。或者,如果最后一个不是对象,则它本身。

示例

extend(, a:1, a:2);            // a:2
extend(, a:1, b:2);            // a:1, b:2
extend(, a: b:1, a: b:2);  // a: b:2
extend(, a: b:1, a: c:2);  // a: b:2, c:2
extend(, a: a:1, a: b:2, a: 'whatever non object');
    // a: "whatever non object"
extend(, a: a:1, a: b:2, a: 'whatever non object', a: c:3,a: d:4);
    // a: c:3, d:4

警告

请注意,如果浏览器不够聪明,它可能会陷入无限循环:

var obj1=,
    obj2=;
obj1.me=obj1;
obj2.me=obj2;
extend(,obj1,obj2);

如果浏览器足够聪明,它可以抛出错误,或者返回me: undefined,或者其他什么。

请注意,如果您使用 jQuery 的 $.extend,此警告也适用。

【讨论】:

【参考方案2】:

现在所有现代浏览器都支持 ES2015,原生 Object.assign 可用于扩展对象

Object.assign(, _default, values)

Object.assign

注意default是保留关键字,不能用作变量名


原始答案,写于 2013 年:

由于这是用 jQuery 标记的,您可以使用 $.extend 来获得简单的跨浏览器解决方案

var temp = ;
$.extend(true, temp, _default, values);
values = temp;

【讨论】:

但这会改变我的默认'default_array',我不想要它,因为它是一个全局变量...... @amp - 这很容易通过使用临时对象来避免。 这个不行,ghi会被覆盖,不会合并在一起。 是的,正在嵌套的 json 中删除现有对象【参考方案3】:

如果你对 ES6 满意的话:

Object.assign(, default, values)

【讨论】:

如果默认值存在,则仍然覆盖【参考方案4】:

另一种方法是在默认情况下简单地扩展值(而不是覆盖默认值的其他方式)

Object.assign(value, default) // values of default overrides value
default = value // reset default to value

这里需要注意的是 value 的内容也发生了变化。好处是不需要库,并且比上面的普通解决方案更容易。

【讨论】:

【参考方案5】:

我基于 Oriol 的回答的版本添加了对数组的检查,这样数组就不会变成有趣的 '0': ..., '1': ... thingys

function extend (target) 
  for(var i=1; i<arguments.length; ++i) 
    var from = arguments[i];
    if(typeof from !== 'object') continue;
    for(var j in from) 
      if(from.hasOwnProperty(j)) 
        target[j] = typeof from[j]==='object' && !Array.isArray(from[j])
          ? extend(, target[j], from[j])
          : from[j];
      
    
  
  return target;

【讨论】:

【参考方案6】:

我发现最简单的方法是使用来自 lodash (https://lodash.com/docs/4.17.15#mergeWith) 的 mergeWith(),它接受一个自定义函数,该函数决定如何处理每个属性合并。这是递归的:

const mergeFunction = (objValue, srcValue) => 
  if (typeof srcValue === 'object') 
    _.mergeWith(objValue, srcValue, mergeFunction)
   else if (objValue) 
    return objValue
   else 
    return srcValue
  

【讨论】:

以上是关于合并两个对象而不覆盖的主要内容,如果未能解决你的问题,请参考以下文章

合并 JS 对象而不覆盖

合并两个数组而不覆盖[重复]

合并表而不覆盖mysql phpmyadmin中的现有表

Coredata 合并关系而不是覆盖它

JQuery使用JQuery 合并两个 json 对象

将对象添加到数组而不覆盖