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


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

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

obj1.merge(obj2);

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

有没有人有这个脚本或知道内置的方式来做到这一点?我不需要递归,我不需要合并函数,只需要平面对象上的方法。


1831


起源


Object.assign .... - caub


答案:


ECMAScript 2018标准方法

你会用的 对象传播

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

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

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

1898



如果对象具有相同的名称属性,并且您还希望合并属性,则此方法不起作用。 - Xiè Jìléi
这只是浅复制/合并。有可能破坏很多元素。 - Jay Taylor
+1表示承认一些可怜的灵魂被迫使用废弃原型的框架...... - thejonwithnoh
Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1 对于构建新对象的所示实现,这是不正确的。 - scones
这对我来说不起作用,因为“因此它分配属性而不仅仅是复制或定义新属性。如果合并源包含getter,这可能使它不适合将新属性合并到原型中。” (developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...)。我不得不使用 var merged = {...obj1, ...obj2}。 - morgler


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.

1115



天哪,名字很差。在搜索如何合并对象时,不太可能找到它。 - Gregory
小心:但是会修改变量“settings”。 jQuery不返回新实例。这个(以及命名)的原因是.extend()被开发用于扩展对象,而不是将东西混合在一起。如果你想要一个新对象(例如,你不想触摸默认设置),你可以随时使用jQuery.extend({},settings,options); - webmat
请注意,jQuery.extend还有一个深度(布尔)设置。 jQuery.extend(true,settings,override),如果设置中的属性包含对象并且覆盖仅包含该对象的一部分,则这很重要。深度设置只会更新它存在的位置,而不是删除不匹配的属性。默认值为false。 - vol7ron
同样,如果你使用underscore.js那就是 underscorejs.org/#extend - twmulloy
Gee willikers,他们确实做得很好。如果您搜索如何扩展Javascript对象,它就会出现!如果你试图融合或拼接Javascript对象,你可能不会碰到它。 - Derek Greer


和谐ECMAScript 2015(ES6) 指定 Object.assign 这会做到这一点。

Object.assign(obj1, obj2);

目前的浏览器支持是 越来越好,但如果你正在为没有支持的浏览器开发,你可以使用 填充工具


310



请注意,这只是一个浅层合并 - Joachim Lous
我想同时 Object.assign 有相当不错的报道: kangax.github.io/compat-table/es6/... - LazerBass
这应该是正确的答案。现在人们编译他们的代码以与不支持此类事物的罕见浏览器(IE11)兼容。附注:如果您不想将obj2添加到obj1,则可以使用返回新对象 Object.assign({}, obj1, obj2) - Duvrai
@Duvrai根据我见过的报道, IE11绝对不是罕见的 截至2016年7月,市场份额约为18%。 - NanoWizard
@Kermani OP特别指出递归是不必要的。因此,虽然知道此解决方案执行浅合并是有用的,但这个答案是足够的。 JoachimLou的评论得到了当之无愧的赞成,并在需要时提供了额外的信息。我认为深度和浅层合并以及类似主题之间的区别超出了本讨论的范围,更适合其他SO问题。 - NanoWizard


我用google搜索代码来合并对象属性,最后来到这里。但是由于没有任何递归合并的代码,我自己写了。 (也许jQuery扩展是递归BTW?)无论如何,希望其他人也会觉得它很有用。

(现在代码不使用 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 } } };

227



很好,但我会首先对物体进行深度复制。这样o1也会被修改,因为对象是通过引用传递的。 - skerit
这就是我想要的。确保在转到try catch语句之前检查是否obj2.hasOwnProperty(p)。否则,你最终可能会从原型链中的较高位置合并其他一些垃圾。 - Adam
谢谢马库斯。请注意,o1也由MergeRecursive修改,因此最后,o1和o3是相同的。如果您不返回任何内容可能会更直观,因此用户知道他们提供的内容(o1)会被修改并成为结果。另外,在你的例子中,可能值得在o2中添加一些原来不在o1中的东西,以显示o2属性被插入到o1中(其他人下面提交的替代代码复制你的例子,但当o2包含属性时它们会中断在o1 - 但你的工作在这方面)。 - pancake
@JonoRR - 对它自身的调用使它递归:obj1 [p] = MergeRecursive(obj1 [p],obj2 [p]); - z8000
这是一个很好的答案,但有几个狡辩:1)没有逻辑应该基于异常;在这种情况下,任何抛出的异常都会被捕获到不可预测的结果。 2)我同意@pancake关于不返回任何内容的评论,因为原始对象被修改了。这是一个没有异常逻辑的jsFiddle示例:jsfiddle.net/jlowery2663/z8at6knn/4 - Jeff Lowery - Jeff Lowery


注意 underscore.jsextend-方法 这是一个单线程:

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

166



这个例子很好,因为你正在处理匿名对象,但如果不是这样,那么webmat就是 在jQuery答案中评论 关于突变的警告在这里适用,也作为下划线 改变目标对象。就像jQuery的答案一样,为了做到这一点,无变化只需合并到一个空对象: _({}).extend(obj1, obj2); - Abe Voelker
根据你想要实现的目标,还有另一个可能具有相关性: _.defaults(object, *defaults) “使用默认对象中的值填充对象中的null和undefined属性,然后返回该对象。” - conny
许多人已经评论过改变目标对象,但是不是让方法创建一个新的,一个常见的模式(例如在dojo中)就是说dojo.mixin(dojo.clone(myobj),{newpropertys:“来添加到myobj“}) - Colin D
下划线可以采用任意数量的对象,因此您可以通过执行来避免原始对象的突变 var mergedObj = _.extend({}, obj1, obj2)。 - lobati


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

81



你也可以从JQ源“借用”扩展的定义...... - juanmf
@juanmf是为了什么? - naXa
@naXa我认为如果你不想为这个fn添加一个lib,那将是一个选择。 - juanmf


我今天需要合并对象,这个问题(和答案)对我帮助很大。我尝试了一些答案,但没有一个符合我的需求,所以我结合了一些答案,自己添加了一些东西,并提出了一个新的合并功能。这里是:

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

59



这是一个很好的答案!它就像jQuery源代码! - Progo
问题的唯一标签是 javascript,这是唯一正确的答案,也是很好的答案。 - skobaljic
很好的答案,但我认为如果第一个和第二个属性存在,你试图通过合并第一个和第二个来创建一个新对象,那么第一个对象中存在的唯一对象将丢失 - Strikers
但这不是预期的行为吗?此函数的用途是参数顺序中的覆盖值。下一个将覆盖当前。你怎么能保留独特的价值观?从我的例子来看,你对此有何看法 merge({}, t1, { key1: 1 })? - Emre Erkan
我的期望是只合并非对象类型的值。 - AGamePlayer


您可以使用 对象传播属性 - 目前是第3阶段ECMAScript提案。

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

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


43



浏览器支持? - Alph.Dev
@ Alph.Dev你知道caniuse.com吗? - connexo
@connexo你问的目的是什么?在caniuse.com上提供JS对象传播属性的正确链接或不干涉。这不是一个玩智能屁股的地方。 - Alph.Dev
这不是一个要求每个人都可以轻松查看的东西的地方。 - connexo
我无法在node.js中使用3点语法。节点抱怨它。 - blackhawk


应修改给定的解决方案以进行检查 source.hasOwnProperty(property) 在里面 for..in 在分配之前循环 - 否则,你最终会复制整个原型链的属性,这很少需要...


37



这个答案应该是一个评论,但无论如何正确和重要...... - idmean


在一行代码中合并N个对象的属性

一个 Object.assign method是ECMAScript 2015(ES6)标准的一部分,完全符合您的要求。 (IE 不支持)

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

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

阅读更多...

填充工具 支持旧浏览器:

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

33



什么'使用严格'做旧浏览器或为什么它在那里。支持对象的浏览器[defineProperty;键; getOwnPropertyDescriptor;等等],并不是很老。它们只是gen.5 UAs的早期版本。 - Bekim Bacaj
谢谢你的澄清 Objects.assign 返回一个合并的对象,其他答案不会。这是更具功能性的编程风格。 - Sridhar-Sarnobat