如何动态合并两个JavaScript对象的属性?

3256

我需要能够在运行时合并两个(非常简单的)JavaScript对象。例如,我想要:

var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog' }

obj1.merge(obj2);

//obj1 now has three properties: food, car, and animal

有没有内置的方法可以做到这一点?我不需要递归,也不需要合并函数,只需要在平面对象上使用方法。


值得注意的是,类似问题的这个答案展示了如何向下合并“一级”。也就是说,它合并了重复键的值(而不是用第二个值覆盖第一个值),但不会进一步递归。在我看来,这是针对该任务的优秀干净代码。 - ToolmakerSteve
顺便说一句,前几个答案会进行“浅层合并”:如果obj1和obj2中都存在相同的键,则obj2中的值会被保留,而obj1中的值则会被丢弃。例如,如果问题的示例是var obj2 = { animal: 'dog', food: 'bone' };,则合并结果将为{ food: 'bone', car: 'ford', animal: 'dog' }。如果您正在处理“嵌套数据”,并希望进行“深度合并”,那么请查找提到“深度合并”或“递归”的答案。如果您有数组值,请使用github “TehShrike/deepmerge”的“arrayMerge”选项,如此处所述。 - ToolmakerSteve
69个回答

4012

ECMAScript 2018标准方法

您可以使用对象扩展运算符

let merged = {...obj1, ...obj2};

merged 现在是 obj1obj2 的并集。在 obj2 中的属性将覆盖 obj1 中的属性。

/** There's no limit to the number of objects you can merge.
 *  Later properties overwrite earlier properties with the same name. */
const allRules = {...obj1, ...obj2, ...obj3};

这里还有MDN文档关于这个语法的内容。如果你正在使用babel,你需要@babel/plugin-proposal-object-rest-spread插件才能使其工作(该插件包含在@babel/preset-env中,ES2018)。

ECMAScript 2015 (ES6)标准方法

/* For the case in question, you would do: */
Object.assign(obj1, obj2);

/** There's no limit to the number of objects you can merge.
 *  All objects get merged into the first object. 
 *  Only the object in the first argument is mutated and returned.
 *  Later properties overwrite earlier properties with the same name. */
const allRules = Object.assign({}, obj1, obj2, obj3, etc);

(请参见MDN JavaScript 参考文档


ES5及更早版本的方法

for (var attrname in obj2) { obj1[attrname] = obj2[attrname]; }

请注意,这将仅将obj2的所有属性添加到obj1中,如果您仍然想使用未修改的obj1,则可能不是您想要的。
如果您正在使用一个框架来破坏您的原型,那么您必须使用像hasOwnProperty这样的检查进行更高级的操作,但是该代码将适用于99%的情况。
示例函数:
/**
 * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
 * @param obj1
 * @param obj2
 * @returns obj3 a new object based on obj1 and obj2
 */
function merge_options(obj1,obj2){
    var obj3 = {};
    for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
    for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
    return obj3;
}

此外:点击这里查看程序,以了解Object.assign和扩展语法对象字面量之间的区别。

125
如果对象具有相同的名称属性,这种方法就不起作用了,您还需要合并这些属性。 - Lenik
84
这只进行浅层复制/合并,可能会覆盖许多元素。 - Jay Taylor
61
感谢您承认有些不幸的人被迫使用会破坏他们原本设计的框架。+1 - thejonwithnoh
10
使用Object.assign(obj1, obj2)可能更好,因为let merged = {...obj1, ...obj2}会创建一个新对象,并将obj1obj2的属性复制到其中,这对于大型对象可能非常昂贵。assign方法也修改了obj1,符合问题中预期的行为。 - SePeF
查看Object.assign和展开语法对象字面量之间的差异,请参阅https://jsfiddle.net/fmcajhsv/。我希望这个能对你有所帮助。 - Harsh Patel

1239

jQuery也为此提供了一个实用工具:http://api.jquery.com/jQuery.extend/

摘自jQuery文档:

// Merge options object into settings object
var settings = { validate: false, limit: 5, name: "foo" };
var options  = { validate: true, name: "bar" };
jQuery.extend(settings, options);

// Now the content of settings object is the following:
// { validate: true, limit: 5, name: "bar" }

上述代码将修改名为 settings现有对象


如果您想创建一个不修改任何参数的新对象,请使用以下代码:

var defaults = { validate: false, limit: 5, name: "foo" };
var options = { validate: true, name: "bar" };

/* Merge defaults and options, without modifying defaults */
var settings = $.extend({}, defaults, options);

// The content of settings variable is now the following:
// {validate: true, limit: 5, name: "bar"}
// The 'defaults' and 'options' variables remained the same.

176
注意:变量"settings"将被修改,jQuery 不会返回新的实例。这样做的原因(以及命名的原因)是 .extend() 是用来扩展对象的,而不是把东西混在一起的。如果你想要一个新的对象(例如 settings 是默认值,你不想改变它),你可以随时使用 jQuery.extend({}, settings, options); - webmat
32
顺便提一下,jQuery.extend还有一个深度(布尔值)设置。jQuery.extend(true,settings,override),如果设置中的属性包含一个对象,而覆盖只有该对象的一部分,则此设置非常重要。 深度设置不会删除不匹配的属性,而是仅在存在的情况下更新。 默认值为false。 - vol7ron

389

49
请注意,这只是一次浅层合并。 - Joachim Lous
我认为与此同时Object.assign的覆盖范围相当不错:http://kangax.github.io/compat-table/es6/#test-Object_static_methods_Object.assign - brass monkey
17
现在应该是正确的答案了。现今的人们编译他们的代码以兼容那些不支持这种功能的罕见浏览器(IE11)。另外,如果你不想将obj2添加到obj1中,你可以使用 Object.assign({}, obj1, obj2) 返回一个新对象。 - Duvrai
6
根据我看到的报道,截至2016年7月份,IE11在市场份额中占比约为18%,绝对不算稀有。 - NanoWizard

296

我在谷歌上搜索合并对象属性的代码,最终来到了这里。然而,由于没有递归合并的代码,我自己写了一个。(顺便说一句,也许jQuery的extend方法是递归的?)无论如何,希望其他人也会发现它有用。

(现在代码不再使用Object.prototype :)

代码

/*
* Recursively merge properties of two objects 
*/
function MergeRecursive(obj1, obj2) {

  for (var p in obj2) {
    try {
      // Property in destination object set; update its value.
      if ( obj2[p].constructor==Object ) {
        obj1[p] = MergeRecursive(obj1[p], obj2[p]);

      } else {
        obj1[p] = obj2[p];

      }

    } catch(e) {
      // Property in destination object not set; create it and set its value.
      obj1[p] = obj2[p];

    }
  }

  return obj1;
}

一个例子

o1 = {  a : 1,
        b : 2,
        c : {
          ca : 1,
          cb : 2,
          cc : {
            cca : 100,
            ccb : 200 } } };

o2 = {  a : 10,
        c : {
          ca : 10,
          cb : 20, 
          cc : {
            cca : 101,
            ccb : 202 } } };

o3 = MergeRecursive(o1, o2);

生成类似 o3 的对象

o3 = {  a : 10,
        b : 2,
        c : {
          ca : 10,
          cb : 20,
          cc : { 
            cca : 101,
            ccb : 202 } } };

11
好的,但我建议首先对对象进行深拷贝。这样做可以防止由于对象是按引用传递而修改 o1 对象。 - Jelle De Loecker
这段代码介绍了一个原型污染漏洞:`var data = {}; var json = '{"a": "1", "__proto__": { "x": "pwnd" }}'; MergeRecursive(data, JSON.parse(json)); var data2 = {}; console.log(data2.x);` - undefined

180

请注意,underscore.jsextend-方法可以在一行代码中完成此操作:

_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}

39
如果你在处理匿名对象,则此示例很好,但如果不是这种情况,则 webmat 在 jQuery 答案中的评论中警告关于突变,因为 underscore 也会突变目标对象。与 jQuery 答案一样,要实现无突变,请将其合并到一个空对象中:_({}).extend(obj1, obj2); - Abe Voelker

83

与jQuery extend()类似,在AngularJS中也有相同的函数:

// Merge the 'options' object into the 'settings' object
var settings = {validate: false, limit: 5, name: "foo"};
var options  = {validate: true, name: "bar"};
angular.extend(settings, options);

65

今天我需要合并对象,这个问题(以及答案)帮了我很多忙。我尝试了一些答案,但没有一个符合我的需求,所以我结合了一些答案,加入了一些自己的东西,得出了一个新的合并函数。以下是它:

var merge = function() {
    var obj = {},
        i = 0,
        il = arguments.length,
        key;
    for (; i < il; i++) {
        for (key in arguments[i]) {
            if (arguments[i].hasOwnProperty(key)) {
                obj[key] = arguments[i][key];
            }
        }
    }
    return obj;
};

一些示例用法:

var t1 = {
    key1: 1,
    key2: "test",
    key3: [5, 2, 76, 21]
};
var t2 = {
    key1: {
        ik1: "hello",
        ik2: "world",
        ik3: 3
    }
};
var t3 = {
    key2: 3,
    key3: {
        t1: 1,
        t2: 2,
        t3: {
            a1: 1,
            a2: 3,
            a4: [21, 3, 42, "asd"]
        }
    }
};

console.log(merge(t1, t2));
console.log(merge(t1, t3));
console.log(merge(t2, t3));
console.log(merge(t1, t2, t3));
console.log(merge({}, t1, { key1: 1 }));

63

你可以使用对象展开语法来实现这个目标。它是ES2018及更高版本的一部分。

const obj1 = { food: 'pizza', car: 'ford' };
const obj2 = { animal: 'dog' };

const obj3 = { ...obj1, ...obj2 };
console.log(obj3);


浏览器支持? - Slava
1
@Alph.Dev 你知道 caniuse.com 吗? - connexo
1
我无法在node.js中使用三个点的语法。Node会报错。 - klewis
三个点(...)代表什么? - Merrin K
它被称为扩展语法,可在需要包含对象或数组元素的新数组或对象中使用。非常巧妙。请查看此链接 - [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax] - Hari Reddy

42

为避免复制整个原型链的属性,应修改给出的解决方案,在for..in循环中添加检查语句source.hasOwnProperty(property)后再进行赋值,这在大多数情况下是不需要的...


41

将N个对象的属性合并成一行代码

Object.assign方法是ECMAScript 2015(ES6)标准的一部分,正是你所需要的。(IE不支持)

var clone = Object.assign({}, obj);

Object.assign() 方法被用来将一个或多个源对象的所有可枚举属性值复制到目标对象中。

了解更多...

支持旧版浏览器的polyfill(填充)

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接