¿Cómo puedo fusionar propiedades de dos objetos JavaScript dinámicamente?

2521

Necesito poder fusionar dos objetos JavaScript (muy simples) en tiempo de ejecución. Por ejemplo, me gustaría:

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

obj1.merge(obj2);

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

¿Alguien tiene un script para esto o sabe de una manera integrada de hacer esto? No necesito recursividad, y no necesito fusionar funciones, solo métodos en objetos planos.

JC Grubbs
fuente
Vale la pena señalar esta respuesta en una pregunta similar , que muestra cómo fusionar "un nivel hacia abajo". Es decir, fusiona valores de claves duplicadas (en lugar de sobrescribir el primer valor con el segundo valor), pero no se repite más allá de eso. En mi humilde opinión, es un buen código limpio para esa tarea.
ToolmakerSteve
Por cierto, las pocas respuestas principales hacen una fusión "superficial": si existe la misma clave en obj1 y obj2, el valor en obj2 se mantiene, el valor en obj1 se descarta. Por ejemplo, si el ejemplo de la pregunta tuviera var obj2 = { animal: 'dog', food: 'bone' };, la fusión sería { food: 'bone', car: 'ford', animal: 'dog' }. Si está trabajando con "datos anidados" y desea una "fusión profunda", busque respuestas que mencionen "fusión profunda" o "recursión". Si tiene valores que son arrays, entonces use la opción "arrayMerge" de github "TehShrike / deepmerge", como se menciona aquí .
ToolmakerSteve
Siga el siguiente enlace stackoverflow.com/questions/171251/… El operador de propagación, que es la nueva característica en la versión ES6
Dev216

Respuestas:

2909

Método estándar de ECMAScript 2018

Se podría utilizar objeto propagación :

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

mergedahora es la unión de obj1y obj2. Las propiedades en obj2sobrescribirán aquellas en 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};

Aquí también está la documentación de MDN para esta sintaxis. Si está utilizando babel, necesitará el complemento babel-plugin-transform-object-rest-spread para que funcione.

Método estándar de 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);

(vea la referencia de JavaScript de MDN )


Método para ES5 y anteriores

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

Tenga en cuenta que esto sólo tiene que añadir todos los atributos de obj2a obj1lo que podría no ser lo que quiera si aún desea utilizar el no modificado obj1.

Si está utilizando un marco que arruina todos sus prototipos, entonces debe ser más sofisticado con controles similares hasOwnProperty, pero ese código funcionará en el 99% de los casos.

Función de ejemplo:

/**
 * 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;
}
Derek Ziemba
fuente
90
Esto no funciona si los objetos tienen los mismos atributos de nombre, y también querría fusionar los atributos.
Xiè Jìléi
69
Esto solo hace una copia / fusión superficial. Tiene el potencial de golpear muchos elementos.
Jay Taylor
45
+1 por reconocer que algunas almas pobres se ven obligadas a usar marcos que
arruinan
44
Esto no funcionó para mí debido a "Por lo tanto, asigna propiedades en lugar de simplemente copiar o definir nuevas propiedades. Esto puede hacer que no sea adecuado fusionar nuevas propiedades en un prototipo si las fuentes de fusión contienen captadores". ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ). Tuve que usar var merged = {...obj1, ...obj2}.
morgler
2
@ Ze'ev{...obj1, ...{animal: 'dog'}}
barra invertida112
1191

jQuery también tiene una utilidad para esto: http://api.jquery.com/jQuery.extend/ .

Tomado de la documentación de 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" }

El código anterior mutará el objeto existente nombrado settings.


Si desea crear un nuevo objeto sin modificar ninguno de los argumentos, use esto:

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.
Avdi
fuente
166
Cuidado: la variable "configuración" será modificada, sin embargo. jQuery no devuelve una nueva instancia. La razón de esto (y de los nombres) es que .extend () fue desarrollado para extender objetos, en lugar de juntar cosas. Si desea un nuevo objeto (por ejemplo, la configuración es la predeterminada que no desea tocar), siempre puede jQuery.extend ({}, configuración, opciones);
webmat
1
¡Tocar el asunto exacto! Gracias un montón. Como dijo el comentario anterior, está mal nombrado. Busqué documentos de jQuery en el cuarto y cuarto y no me encontré con él.
Mike Starov
28
Eso sí, jQuery.extend también tiene una configuración profunda (booleana). jQuery.extend(true,settings,override), que es importante si una propiedad en la configuración contiene un objeto y la anulación solo tiene parte de ese objeto. En lugar de eliminar las propiedades incomparables, la configuración profunda solo se actualizará donde exista. El valor predeterminado es falso.
vol7ron
19
Gee willikers, realmente hicieron un buen trabajo nombrando esto. Si busca cómo extender un objeto Javascript, ¡aparece de inmediato! Sin embargo, es posible que no lo golpee si está tratando de fusionar o unir objetos Javascript.
Derek Greer
2
No le diga a la gente que considere usar un método de biblioteca cuando un par de líneas de vanilla JS hagan lo que quieran. Hubo un tiempo antes de jQuery!
CrazyMerlin
348

El Harmony ECMAScript 2015 (ES6) especifica Object.assignqué hará esto.

Object.assign(obj1, obj2);

El soporte actual del navegador está mejorando , pero si está desarrollando para navegadores que no tienen soporte, puede usar un polyfill .

NanoWizard
fuente
35
Tenga en cuenta que esto es solo una fusión superficial
Joachim Lous
Mientras tanto, creo que Object.assigntiene una cobertura bastante decente: kangax.github.io/compat-table/es6/…
LazerBass
13
Esta debería ser la respuesta correcta. La gente hoy en día compila su código para que sea compatible con el navegador raro (IE11) que no admite este tipo de cosas. Nota al margen: si no desea agregar obj2 a obj1, puede devolver un nuevo objeto usandoObject.assign({}, obj1, obj2)
Duvrai
66
@Duvrai Según los informes que he visto, IE11 definitivamente no es raro en alrededor del 18% de la cuota de mercado a partir de julio de 2016.
NanoWizard
3
@Kermani El OP señala específicamente que la recursividad es innecesaria. Por lo tanto, si bien es útil saber que esta solución realiza una fusión superficial, esta respuesta es suficiente como es. El comentario de JoachimLou ha recibido votos bien merecidos y proporciona esa información adicional cuando es necesario. Creo que la diferencia entre la fusión profunda y superficial y temas similares está más allá del alcance de esta discusión y se adapta mejor a otras preguntas SO.
NanoWizard
264

Busqué en Google el código para fusionar las propiedades del objeto y terminé aquí. Sin embargo, como no había ningún código para la fusión recursiva, lo escribí yo mismo. (¿Quizás jQuery extend es recursivo, por cierto?) De todos modos, espero que alguien más lo encuentre útil también.

(Ahora el código no usa Object.prototype:)

Código

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

Un ejemplo

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

Produce el objeto o3 como

o3 = {  a : 10,
        b : 2,
        c : {
          ca : 10,
          cb : 20,
          cc : { 
            cca : 101,
            ccb : 202 } } };
Markus
fuente
11
Bien, pero primero haría una copia profunda de los objetos. De esta manera, o1 también se modificaría, ya que los objetos se pasan por referencia.
skerit
1
Esto era lo que estaba buscando. Asegúrese de verificar si obj2.hasOwnProperty (p) antes de ir a la declaración try catch. De lo contrario, podría terminar con alguna otra basura que se fusiona desde más arriba en la cadena de prototipos.
Adam
3
Gracias Markus Tenga en cuenta que o1 también es modificado por MergeRecursive, por lo que al final, o1 y o3 son idénticos. Puede ser más intuitivo si no devuelve nada, por lo que el usuario sabe que lo que está suministrando (o1) se modifica y se convierte en el resultado. Además, en su ejemplo, podría valer la pena agregar algo en o2 que no esté originalmente en o1, para mostrar que las propiedades de o2 se insertan en o1 (alguien más a continuación envió un código alternativo copiando su ejemplo, pero se rompe cuando o2 contiene propiedades no en o1, pero el suyo funciona a este respecto).
panqueque
77
Esta es una buena respuesta, pero un par de dudas: 1) Ninguna lógica debe basarse en excepciones; en este caso, cualquier excepción lanzada se detectará con resultados impredecibles. 2) Estoy de acuerdo con el comentario de @ pancake sobre no devolver nada, ya que se modifica el objeto original. Aquí hay un ejemplo de jsFiddle sin la lógica de excepción: jsfiddle.net/jlowery2663/z8at6knn/4 - Jeff Lowery
Jeff Lowery
3
Además, obj2 [p] .constructor == Array es necesario en cualquier caso donde haya una matriz de objetos.
El compositor
175

Tenga en cuenta que underscore.js's extend-method hace esto en una línea:

_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}
industrial
fuente
36
Este ejemplo está bien ya que se trata de objetos anónimos, pero si este no es el caso, entonces el comentario de webmat en la advertencia de respuesta jQuery sobre mutaciones se aplica aquí, ya que el guión bajo también muta el objeto de destino . Al igual que la respuesta de jQuery, para hacer esto sin mutación, simplemente combínelo en un objeto vacío:_({}).extend(obj1, obj2);
Abe Voelker
8
Hay otra que puede ser relevante según lo que intente lograr: _.defaults(object, *defaults)"Rellene las propiedades nulas e indefinidas en el objeto con valores de los objetos predeterminados y devuelva el objeto".
conny
Muchas personas han comentado sobre la mutación del objeto de destino, pero en lugar de que un método cree uno nuevo, un patrón común (en dojo, por ejemplo) es decir dojo.mixin (dojo.clone (myobj), {newpropertys: "para agregar to myobj "})
Colin D
77
El subrayado puede tomar un número arbitrario de objetos, por lo que puede evitar la mutación del objeto original haciendo var mergedObj = _.extend({}, obj1, obj2).
lobati
84

Similar a jQuery extend (), tiene la misma función en 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);
AndreasE
fuente
1
@naXa Creo que sería una opción si no desea agregar una lib solo para este fn.
juanmf
67

Necesito fusionar objetos hoy, y esta pregunta (y respuestas) me ayudaron mucho. Intenté algunas de las respuestas, pero ninguna de ellas se ajustaba a mis necesidades, por lo que combiné algunas de las respuestas, agregué algo y obtuve una nueva función de combinación. Aquí está:

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

Algunos usos de ejemplo:

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 }));
Emre Erkan
fuente
3
buena respuesta, pero creo que si una propiedad está presente en la primera y en la segunda y está tratando de hacer un nuevo objeto fusionando la primera y la segunda, se perderán las únicas presentes en el primer objeto
Strikers
2
¿Pero no es comportamiento esperado? El propósito de esta función es anular valores en orden de argumento. El siguiente anulará la corriente. ¿Cómo puedes preservar valores únicos? De mi ejemplo, ¿qué esperas de merge({}, t1, { key1: 1 })?
Emre Erkan
Mi expectativa es fusionar solo valores sin tipo de objeto.
AGamePlayer
falla si el resultado esperado es el siguiente: gist.github.com/ceremcem/a54f90923c6e6ec42c7daf24cf2768bd
ceremcem
Esto funcionó para mí en modo estricto para mi proyecto node.js. Hasta ahora, esta es la mejor respuesta en esta publicación.
klewis
48

Puede usar propiedades de propagación de objetos, actualmente una propuesta ECMAScript de etapa 3.

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

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

Jaime Asm
fuente
Soporte de navegador?
Alph.Dev
1
@ Alph.Dev ¿Conoces caniuse.com?
connexo
No puedo hacer que la sintaxis de 3 puntos funcione en node.js. El nodo se queja de ello.
Klewis
41

Las soluciones dadas deben modificarse para verificar source.hasOwnProperty(property)los for..inbucles antes de asignar; de lo contrario, terminará copiando las propiedades de toda la cadena de prototipos, lo que rara vez se desea ...

Christoph
fuente
40

Combinar propiedades de N objetos en una línea de código

Un Object.assignmétodo es parte del estándar ECMAScript 2015 (ES6) y hace exactamente lo que necesita. ( IEno compatible)

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

El método Object.assign () se utiliza para copiar los valores de todas las propiedades propias enumerables de uno o más objetos de origen a un objeto de destino.

Lee mas...

El polyfill para admitir navegadores antiguos:

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;
    }
  });
}
Eugene Tiurin
fuente
¿Qué hace "uso estricto" en los navegadores antiguos o por qué está allí? navegadores que admiten objetos [defineProperty; llaves; getOwnPropertyDescriptor; etc], no son exactamente viejos. Son solo versiones anteriores de UA gen.5.
Bekim Bacaj
Gracias por aclarar que Objects.assigndevuelve un objeto combinado que las otras respuestas no. Ese es un estilo de programación más funcional.
Sridhar Sarnobat
36

Los dos siguientes son probablemente un buen punto de partida. ¡lodash también tiene una función de personalización para esas necesidades especiales!

_.extend( http://underscorejs.org/#extend )
_.merge( https://lodash.com/docs#merge )

appsmatics
fuente
44
Tenga en cuenta que los métodos anteriores mutan el objeto original.
Wtower
El método lodash funcionó para mí, ya que quería fusionar dos objetos y retener propiedades anidadas a más de uno o dos niveles de profundidad (en lugar de simplemente reemplazarlas / borrarlas).
SamBrick
verdadero @Wtower. Acabo de tener un error debido a esto. Mejor asegúrese de que siempre comience como _.merge ({}, ...
Martin Cremer
29

Por cierto, todo lo que estás haciendo es sobrescribir propiedades, no fusionar ...

Así es como realmente se fusionó el área de objetos de JavaScript: solo tose sobrescribirán las claves del objeto que no sean objetos en sí from. Todo lo demás se fusionará realmente . Por supuesto, puede cambiar este comportamiento para no sobrescribir nada que exista, solo si to[n] is undefined, etc.

var realMerge = function (to, from) {

    for (n in from) {

        if (typeof to[n] != 'object') {
            to[n] = from[n];
        } else if (typeof from[n] == 'object') {
            to[n] = realMerge(to[n], from[n]);
        }
    }
    return to;
};

Uso:

var merged = realMerge(obj1, obj2);
Andreas Linden
fuente
3
Me ayudó mucho, el subrayado js extender realmente sobrescribió, en lugar de fusionar
David Cumps
1
Vale la pena señalar que typeof array y typeof null devolverán 'objeto' y lo romperán.
Salakar
@ u01jmg3 vea mi respuesta aquí: stackoverflow.com/questions/27936772/… - la función
isObject
Esto se rompe si se usa en modo estricto para proyectos
node.js
28

Aquí está mi puñalada que

  1. Soporta fusión profunda
  2. No muta argumentos
  3. Toma cualquier cantidad de argumentos
  4. No extiende el prototipo de objeto
  5. No depende de otra biblioteca ( jQuery , MooTools , Underscore.js , etc.)
  6. Incluye cheque para hasOwnProperty
  7. Es corto :)

    /*
        Recursively merge properties and return new object
        obj1 &lt;- obj2 [ &lt;- ... ]
    */
    function merge () {
        var dst = {}
            ,src
            ,p
            ,args = [].splice.call(arguments, 0)
        ;
    
        while (args.length > 0) {
            src = args.splice(0, 1)[0];
            if (toString.call(src) == '[object Object]') {
                for (p in src) {
                    if (src.hasOwnProperty(p)) {
                        if (toString.call(src[p]) == '[object Object]') {
                            dst[p] = merge(dst[p] || {}, src[p]);
                        } else {
                            dst[p] = src[p];
                        }
                    }
                }
            }
        }
    
       return dst;
    }

Ejemplo:

a = {
    "p1": "p1a",
    "p2": [
        "a",
        "b",
        "c"
    ],
    "p3": true,
    "p5": null,
    "p6": {
        "p61": "p61a",
        "p62": "p62a",
        "p63": [
            "aa",
            "bb",
            "cc"
        ],
        "p64": {
            "p641": "p641a"
        }
    }
};

b = {
    "p1": "p1b",
    "p2": [
        "d",
        "e",
        "f"
    ],
    "p3": false,
    "p4": true,
    "p6": {
        "p61": "p61b",
        "p64": {
            "p642": "p642b"
        }
    }
};

c = {
    "p1": "p1c",
    "p3": null,
    "p6": {
        "p62": "p62c",
        "p64": {
            "p643": "p641c"
        }
    }
};

d = merge(a, b, c);


/*
    d = {
        "p1": "p1c",
        "p2": [
            "d",
            "e",
            "f"
        ],
        "p3": null,
        "p5": null,
        "p6": {
            "p61": "p61b",
            "p62": "p62c",
            "p63": [
                "aa",
                "bb",
                "cc"
            ],
            "p64": {
                "p641": "p641a",
                "p642": "p642b",
                "p643": "p641c"
            }
        },
        "p4": true
    };
*/
Paul Spaulding
fuente
En comparación con los otros que probé en esta página, esta función es realmente recursiva (hace una fusión profunda) e imita lo que jQuery.extend () hace realmente bien. Sin embargo, sería mejor modificar el primer objeto / argumento para que un usuario pueda decidir si desea pasar un objeto vacío {}como primer parámetro o si la función modifica el objeto original. Por lo tanto, si cambia dst = {}a dst = arguments[0]él, cambiará el primer objeto que pase al objeto fusionado
Jasdeep Khalsa el
3
Ahora ha dejado de funcionar en cromo. Creo que es una mala idea usar esa construcción toString.call(src) == '[object Object]'para verificar si hay un objeto o no. typeof srces mucho mejor. Además, transforma las matrices en objetos (por lo menos, tengo ese comportamiento en el último Chrome).
m03geek
Necesitaba hacer esto dentro de un bucle codicioso, comparé esta función con la que estaba usando hasta ahora _.merge (objeto, [fuentes]) de lodash (una especie de subrayado lib): su función es casi dos veces más rápida gracias amigo.
Arnaud Bouchot
20

Object.assign ()

ECMAScript 2015 (ES6)

Esta es una nueva tecnología, parte del estándar ECMAScript 2015 (ES6). La especificación de esta tecnología se ha finalizado, pero consulte la tabla de compatibilidad para conocer el uso y el estado de implementación en varios navegadores.

El método Object.assign () se utiliza para copiar los valores de todas las propiedades propias enumerables de uno o más objetos de origen a un objeto de destino. Devolverá el objeto de destino.

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };

var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, target object itself is changed.
Reza Roshan
fuente
19

Para objetos no demasiado complicados, puede usar JSON :

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

objMerge = JSON.stringify(obj1) + JSON.stringify(obj2);

// {"food": "pizza","car":"ford"}{"animal":"dog","car":"chevy"}

objMerge = objMerge.replace(/\}\{/, ","); //  \_ replace with comma for valid JSON

objMerge = JSON.parse(objMerge); // { food: 'pizza', animal: 'dog', car: 'chevy'}
// Of same keys in both objects, the last object's value is retained_/

¡Tenga en cuenta que en este ejemplo "} {" no debe aparecer dentro de una cadena!

Algy
fuente
44
var so1 = JSON.stringify(obj1); var so2 = JSON.stringify(obj1); objMerge = so1.substr(0, so1.length-1)+","+so2.substr(1); console.info(objMerge);
Quamis
function merge(obj1, obj2) { return JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)) .replace(/\}\{/g, ',').replace(/,\}/g, '}').replace(/\{,/g, '{')); }
Alex Ivasyuv
o como una frase:objMerge = JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)).replace(/\}\{/, ", "));
Rabino Shuki Gur
1
@Charles, lo siento, pero ¿cómo es "este método muy peligroso" pero también divertido? Este es el más seguro posible Primero usa JSON para hacer el trabajo pesado de leer el contenido del objeto, segundo, usa el objeto JSON que es el más seguro máquina optimizada para analizar JSO desde un literal de cadena de objeto compatible con JSON. Y también es compatible con IE8, que es el primer navegador en implementar su estándar. Simplemente me hace preguntarme "¿por qué oh por qué tuviste que decir algo tan estúpido y salirte con la tuya"?
Bekim Bacaj
Creo que las funciones en el objeto se descompondrán aquí ya que no se puede usar stringify, pero ¿podemos aclarar otras desventajas?
yeahdixon
18

Hay una biblioteca llamada deepmergeen GitHub : parece estar recibiendo algo de tracción. Es independiente, disponible a través de los administradores de paquetes npm y bower .

Me inclinaría a usar o mejorar esto en lugar de copiar y pegar el código de las respuestas.

Peter Mortensen
fuente
17

La mejor manera de hacerlo es agregar una propiedad adecuada que no sea enumerable mediante Object.defineProperty.

De esta manera, aún podrá iterar sobre las propiedades de sus objetos sin tener la "extensión" recién creada que obtendría si creara la propiedad con Object.prototype.extend.

Esperemos que esto ayude:

Object.defineProperty (Object.prototype, "extend", {
    enumerable: falso,
    valor: función (desde) {
        var props = Object.getOwnPropertyNames (from);
        var dest = this;
        props.forEach (función (nombre) {
            if (nombre en dest) {
                var destination = Object.getOwnPropertyDescriptor (from, name);
                Object.defineProperty (dest, nombre, destino);
            }
        });
        devuelve esto;
    }
});

Una vez que tenga eso funcionando, puede hacer:

var obj = {
    nombre: 'pila',
    acabado: 'desbordamiento'
}
reemplazo var = {
    nombre: 'stock'
};

obj.extend (reemplazo);

Acabo de escribir una publicación de blog al respecto aquí: http://onemoredigit.com/post/1527191998/extending-objects-in-node-js

David Coallier
fuente
Espera un segundo, ¡el código no funciona! :( lo insertó en jsperf para compararlo con otros algoritmos de fusión, y el código falla - la función de extensión no existe
Jens Roland
16

Simplemente puede usar jQuery extend

var obj1 = { val1: false, limit: 5, name: "foo" };
var obj2 = { val2: true, name: "bar" };

jQuery.extend(obj1, obj2);

Ahora obj1contiene todos los valores de obj1yobj2

Dinusha
fuente
2
obj1 contendrá todos los valores, no obj2. Estás extendiendo el primer objeto. jQuery.extend( target [, object1 ] [, objectN ] )
ruuter
55
Esta pregunta no es sobre jQuery. JavaScript! = JQuery
Jorge Riv
1
Busqué en Google JavaScript, pero este es un enfoque más simple
Ehsan
13

El prototipo tiene esto:

Object.extend = function(destination,source) {
    for (var property in source)
        destination[property] = source[property];
    return destination;
}

obj1.extend(obj2) Hará lo que quieras.

efímero
fuente
66
¿Te refieres a Object.prototype.extend? En cualquier caso, no es una buena idea extender Object.prototype. -1.
SolutionYogi el
Ahora que lo pienso, probablemente debería ser Object.prototypey no Object... Buena idea o no, esto es lo que hace el prototype.jsmarco, y si lo está usando u otro marco que dependa de él, Object.prototypeya se extenderá extend.
ephemient el
2
No pretendo insistir en ti, pero decir que está bien porque prototype.js sí es un argumento pobre. Prototype.js es popular pero no significa que sean el modelo a seguir sobre cómo hacer JavaScript. Consulte esta revisión detallada de la biblioteca de prototipos por un profesional de JS. dhtmlkitchen.com/?category=/JavaScript/&date=2008/06/17/…
SolutionYogi
77
¿A quién le importa si está bien o mal? Si funciona, entonces funciona. Esperar la solución perfecta es excelente, excepto cuando se trata de mantener un contrato, ganar nuevos clientes o cumplir con los plazos. Ofrezca a todos los que les apasione programar dinero gratis y eventualmente harán todo de la manera "correcta".
David
1
¿Dónde encontraron a todos ustedes Object.prototype? Object.extendno interferirá con tus objetos, solo adjunta una función a un Objectobjeto. Y obj1.extend(obj2)no es forma correcta de la función de incendios, el uso Object.extend(obj1, obj2)insted
artnikpro
13

** Fusionar objetos es simple usando Object.assigno el ...operador de propagación **

var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog', car: 'BMW' }
var obj3 = {a: "A"}


var mergedObj = Object.assign(obj1,obj2,obj3)
 // or using the Spread operator (...)
var mergedObj = {...obj1,...obj2,...obj3}

console.log(mergedObj);

Los objetos se fusionan de derecha a izquierda, esto significa que los objetos que tienen propiedades idénticas a los objetos a su derecha se anularán.

En este ejemplo, obj2.carreemplazaobj1.car

Leyendas
fuente
12

Solo si alguien está usando la Biblioteca de cierre de Google :

goog.require('goog.object');
var a = {'a': 1, 'b': 2};
var b = {'b': 3, 'c': 4};
goog.object.extend(a, b);
// Now object a == {'a': 1, 'b': 3, 'c': 4};

Existe una función auxiliar similar para la matriz :

var a = [1, 2];
var b = [3, 4];
goog.array.extend(a, b); // Extends array 'a'
goog.array.concat(a, b); // Returns concatenation of array 'a' and 'b'
orian
fuente
10

Extendí el método de David Coallier:

  • Se agregó la posibilidad de fusionar múltiples objetos
  • Apoya objetos profundos
  • anular parámetro (que se detecta si el último parámetro es booleano)

Si la anulación es falsa, no se anula ninguna propiedad, pero se agregarán nuevas propiedades.

Uso: obj.merge (combina ... [, anular]);

Aquí está mi código:

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

Ejemplos y casos de prueba:

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

Mi método igual se puede encontrar aquí: Comparación de objetos en JavaScript

revs gossi
fuente
9

En Ext JS 4 se puede hacer de la siguiente manera:

var mergedObject = Ext.Object.merge(object1, object2)

// Or shorter:
var mergedObject2 = Ext.merge(object1, object2)

Ver merge (object): Object .

Mark
fuente
1
Tenga cuidado con el error EXTJS-9173 en EXT.Object.merge aún después de +2 años aún no resuelto
Chris
9

Wow ... esta es la primera publicación de StackOverflow que he visto con varias páginas. Disculpas por agregar otra "respuesta"

Este método es para ES5 y anteriores : hay muchas otras respuestas que abordan ES6.

No vi ninguna fusión de objetos "profundos" utilizando la argumentspropiedad. Aquí está mi respuesta: compacta y recursiva , que permite pasar argumentos de objeto ilimitados:

function extend() {
    for (var o = {}, i = 0; i < arguments.length; i++) {
        // if (arguments[i].constructor !== Object) continue;
        for (var k in arguments[i]) {
            if (arguments[i].hasOwnProperty(k)) {
                o[k] = arguments[i][k].constructor === Object ? extend(o[k] || {}, arguments[i][k]) : arguments[i][k];
            }
        }
    }
    return o;
}

La parte que se comenta es opcional ... simplemente omitirá los argumentos pasados ​​que no sean objetos (evitando errores).

Ejemplo:

extend({
    api: 1,
    params: {
        query: 'hello'
    }
}, {
    params: {
        query: 'there'
    }
});

// outputs {api: 1, params: {query: 'there'}}

Esta respuesta es ahora solo una gota en el océano ...

Logan
fuente
¡La respuesta más corta que funciona muy bien! ¡Se merece más votos a favor!
Jens Törnell
8
var obj1 = { food: 'pizza', car: 'ford' }
var obj2 = { animal: 'dog' }

// result
result: {food: "pizza", car: "ford", animal: "dog"}

Usando jQuery.extend () - Enlace

// Merge obj1 & obj2 to result
var result1 = $.extend( {}, obj1, obj2 );

Usando _.merge () - Enlace

// Merge obj1 & obj2 to result
var result2 = _.merge( {}, obj1, obj2 );

Usando _.extend () - Enlace

// Merge obj1 & obj2 to result
var result3 = _.extend( {}, obj1, obj2 );

Usando Object.assign () ECMAScript 2015 (ES6) - Enlace

// Merge obj1 & obj2 to result
var result4 = Object.assign( {}, obj1, obj2 );

Salida de todos

obj1: { animal: 'dog' }
obj2: { food: 'pizza', car: 'ford' }
result1: {food: "pizza", car: "ford", animal: "dog"}
result2: {food: "pizza", car: "ford", animal: "dog"}
result3: {food: "pizza", car: "ford", animal: "dog"}
result4: {food: "pizza", car: "ford", animal: "dog"}
Tính Ngô Quang
fuente
7

Basado en la respuesta de Markus y vsync , esta es una versión ampliada. La función toma cualquier número de argumentos. Se puede usar para establecer propiedades en nodos DOM y hacer copias profundas de valores. Sin embargo, el primer argumento se da por referencia.

Para detectar un nodo DOM, se utiliza la función isDOMNode () (consulte la pregunta sobre desbordamiento de pila JavaScript isDOM: ¿cómo se verifica si un objeto JavaScript es un objeto DOM? )

Fue probado en Opera 11, Firefox 6, Internet Explorer 8 y Google Chrome 16.

Código

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

Algunos ejemplos

Establecer innerHTML y el estilo de un elemento HTML

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

Fusionar matrices y objetos. Tenga en cuenta que undefined puede usarse para preservar valores en la matriz / objeto de la izquierda.

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

Cualquier argumento que no sea un objeto JavaScript (incluido nulo) será ignorado. Excepto por el primer argumento, también se descartan los nodos DOM. Tenga en cuenta que las cadenas creadas como una nueva cadena () son, de hecho, objetos.

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

Si desea fusionar dos objetos en un nuevo suministro (sin afectar ninguno de los dos) {} como primer argumento

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

Editar (por ReaperSoon):

Para combinar también matrices

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;
}
Snoozer Man
fuente
6

let obj1 = {a:1, b:2};
let obj2 = {c:3, d:4};
let merged = {...obj1, ...obj2};
console.log(merged);

Tejas Savaliya
fuente
5

Debes usar los valores predeterminados de lodash

_.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
// → { 'user': { 'name': 'barney', 'age': 36 } }
Raphaël
fuente
5

Con Underscore.js , para fusionar una matriz de objetos, haga lo siguiente:

var arrayOfObjects = [ {a:1}, {b:2, c:3}, {d:4} ];
_(arrayOfObjects).reduce(function(memo, o) { return _(memo).extend(o); });

En resultado de:

Object {a: 1, b: 2, c: 3, d: 4}
devmao
fuente