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

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

obj1.merge(obj2);

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

是否有一种内置的方法来实现这一点?我不需要递归,也不需要合并函数,只需要平面对象上的方法。


当前回答

你应该使用lodash的默认值Deep

_.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
// → { 'user': { 'name': 'barney', 'age': 36 } }

其他回答

原型中的正确实现应该如下所示:

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

obj1 = Object.extend(obj1, obj2);
Object.assign(TargetObject, Obj1, Obj2, ...);

我扩展了David Coallier的方法:

增加了合并多个对象的可能性支持深层对象override参数(如果最后一个参数是布尔值,则检测到)

如果覆盖为false,则不会覆盖任何属性,但会添加新的财产。

用法:obj.merge(合并…[,覆盖]);

这是我的代码:

Object.defineProperty(Object.prototype, "merge", {
    enumerable: false,
    value: function () {
        var override = true,
            dest = this,
            len = arguments.length,
            props, merge, i, from;

        if (typeof(arguments[arguments.length - 1]) === "boolean") {
            override = arguments[arguments.length - 1];
            len = arguments.length - 1;
        }

        for (i = 0; i < len; i++) {
            from = arguments[i];
            if (from != null) {
                Object.getOwnPropertyNames(from).forEach(function (name) {
                    var descriptor;

                    // nesting
                    if ((typeof(dest[name]) === "object" || typeof(dest[name]) === "undefined")
                            && typeof(from[name]) === "object") {

                        // ensure proper types (Array rsp Object)
                        if (typeof(dest[name]) === "undefined") {
                            dest[name] = Array.isArray(from[name]) ? [] : {};
                        }
                        if (override) {
                            if (!Array.isArray(dest[name]) && Array.isArray(from[name])) {
                                dest[name] = [];
                            }
                            else if (Array.isArray(dest[name]) && !Array.isArray(from[name])) {
                                dest[name] = {};
                            }
                        }
                        dest[name].merge(from[name], override);
                    } 

                    // flat properties
                    else if ((name in dest && override) || !(name in dest)) {
                        descriptor = Object.getOwnPropertyDescriptor(from, name);
                        if (descriptor.configurable) {
                            Object.defineProperty(dest, name, descriptor);
                        }
                    }
                });
            }
        }
        return this;
    }
});

示例和测试用例:

function clone (obj) {
    return JSON.parse(JSON.stringify(obj));
}
var obj = {
    name : "trick",
    value : "value"
};

var mergeObj = {
    name : "truck",
    value2 : "value2"
};

var mergeObj2 = {
    name : "track",
    value : "mergeObj2",
    value2 : "value2-mergeObj2",
    value3 : "value3"
};

assertTrue("Standard", clone(obj).merge(mergeObj).equals({
    name : "truck",
    value : "value",
    value2 : "value2"
}));

assertTrue("Standard no Override", clone(obj).merge(mergeObj, false).equals({
    name : "trick",
    value : "value",
    value2 : "value2"
}));

assertTrue("Multiple", clone(obj).merge(mergeObj, mergeObj2).equals({
    name : "track",
    value : "mergeObj2",
    value2 : "value2-mergeObj2",
    value3 : "value3"
}));

assertTrue("Multiple no Override", clone(obj).merge(mergeObj, mergeObj2, false).equals({
    name : "trick",
    value : "value",
    value2 : "value2",
    value3 : "value3"
}));

var deep = {
    first : {
        name : "trick",
        val : "value"
    },
    second : {
        foo : "bar"
    }
};

var deepMerge = {
    first : {
        name : "track",
        anotherVal : "wohoo"
    },
    second : {
        foo : "baz",
        bar : "bam"
    },
    v : "on first layer"
};

assertTrue("Deep merges", clone(deep).merge(deepMerge).equals({
    first : {
        name : "track",
        val : "value",
        anotherVal : "wohoo"
    },
    second : {
        foo : "baz",
        bar : "bam"
    },
    v : "on first layer"
}));

assertTrue("Deep merges no override", clone(deep).merge(deepMerge, false).equals({
    first : {
        name : "trick",
        val : "value",
        anotherVal : "wohoo"
    },
    second : {
        foo : "bar",
        bar : "bam"
    },
    v : "on first layer"
}));

var obj1 = {a: 1, b: "hello"};
obj1.merge({c: 3});
assertTrue(obj1.equals({a: 1, b: "hello", c: 3}));

obj1.merge({a: 2, b: "mom", d: "new property"}, false);
assertTrue(obj1.equals({a: 1, b: "hello", c: 3, d: "new property"}));

var obj2 = {};
obj2.merge({a: 1}, {b: 2}, {a: 3});
assertTrue(obj2.equals({a: 3, b: 2}));

var a = [];
var b = [1, [2, 3], 4];
a.merge(b);
assertEquals(1, a[0]);
assertEquals([2, 3], a[1]);
assertEquals(4, a[2]);


var o1 = {};
var o2 = {a: 1, b: {c: 2}};
var o3 = {d: 3};
o1.merge(o2, o3);
assertTrue(o1.equals({a: 1, b: {c: 2}, d: 3}));
o1.b.c = 99;
assertTrue(o2.equals({a: 1, b: {c: 2}}));

// checking types with arrays and objects
var bo;
a = [];
bo = [1, {0:2, 1:3}, 4];
b = [1, [2, 3], 4];

a.merge(b);
assertTrue("Array stays Array?", Array.isArray(a[1]));

a = [];
a.merge(bo);
assertTrue("Object stays Object?", !Array.isArray(a[1]));

a = [];
a.merge(b);
a.merge(bo);
assertTrue("Object overrides Array", !Array.isArray(a[1]));

a = [];
a.merge(b);
a.merge(bo, false);
assertTrue("Object does not override Array", Array.isArray(a[1]));

a = [];
a.merge(bo);
a.merge(b);
assertTrue("Array overrides Object", Array.isArray(a[1]));

a = [];
a.merge(bo);
a.merge(b, false);
assertTrue("Array does not override Object", !Array.isArray(a[1]));

我的equals方法可以在这里找到:JavaScript中的对象比较

在MooTools中,有Object.merge():

Object.merge(obj1, obj2);

基于Markus和vsync的回答,这是一个扩展版本。该函数接受任意数量的参数。它可以用于设置DOM节点上的财产,并对值进行深度复制。然而,第一个论点是通过引用给出的。

要检测DOM节点,使用isDOMNode()函数(请参阅堆栈溢出问题JavaScript isDOM-如何检查JavaScript对象是否为DOM对象?)

它在Opera 11、Firefox 6、Internet Explorer 8和Google Chrome 16中进行了测试。

Code

function mergeRecursive() {

  // _mergeRecursive does the actual job with two arguments.
  var _mergeRecursive = function (dst, src) {
    if (isDOMNode(src) || typeof src !== 'object' || src === null) {
      return dst;
    }

    for (var p in src) {
      if (!src.hasOwnProperty(p))
        continue;
      if (src[p] === undefined)
        continue;
      if ( typeof src[p] !== 'object' || src[p] === null) {
        dst[p] = src[p];
      } else if (typeof dst[p]!=='object' || dst[p] === null) {
        dst[p] = _mergeRecursive(src[p].constructor===Array ? [] : {}, src[p]);
      } else {
        _mergeRecursive(dst[p], src[p]);
      }
    }
    return dst;
  }

  // Loop through arguments and merge them into the first argument.
  var out = arguments[0];
  if (typeof out !== 'object' || out === null)
    return out;
  for (var i = 1, il = arguments.length; i < il; i++) {
    _mergeRecursive(out, arguments[i]);
  }
  return out;
}

一些示例

设置HTML元素的innerHTML和样式

mergeRecursive(
  document.getElementById('mydiv'),
  {style: {border: '5px solid green', color: 'red'}},
  {innerHTML: 'Hello world!'});

合并数组和对象。请注意,undefined可用于保存左侧数组/对象中的值。

o = mergeRecursive({a:'a'}, [1,2,3], [undefined, null, [30,31]], {a:undefined, b:'b'});
// o = {0:1, 1:null, 2:[30,31], a:'a', b:'b'}

任何非JavaScript对象的参数(包括null)都将被忽略。除了第一个参数之外,也会丢弃DOM节点。注意,像new String()这样创建的字符串实际上是对象。

o = mergeRecursive({a:'a'}, 1, true, null, undefined, [1,2,3], 'bc', new String('de'));
// o = {0:'d', 1:'e', 2:3, a:'a'}

如果要将两个对象合并为一个新对象(不影响其中任何一个),请提供{}作为第一个参数

var a={}, b={b:'abc'}, c={c:'cde'}, o;
o = mergeRecursive(a, b, c);
// o===a is true, o===b is false, o===c is false

编辑(由收割者很快):

还要合并阵列

function mergeRecursive(obj1, obj2) {
  if (Array.isArray(obj2)) { return obj1.concat(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 if (Array.isArray(obj2[p])) {
        obj1[p] = obj1[p].concat(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;
}