Como mesclar propriedades de dois objetos JavaScript dinamicamente?


2519

Eu preciso ser capaz de mesclar dois objetos JavaScript (muito simples) em tempo de execução. Por exemplo, eu gostaria de:

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

obj1.merge(obj2);

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

Alguém tem um script para isso ou conhece uma maneira integrada de fazer isso? Não preciso de recursão e não preciso mesclar funções, apenas métodos em objetos simples.


Vale a pena notar esta resposta em uma pergunta semelhante , que mostra como mesclar "um nível abaixo". Ou seja, ele mescla valores de chaves duplicadas (em vez de sobrescrever o primeiro valor pelo segundo valor), mas não recorre mais que isso. IMHO, seu bom código limpo para essa tarefa.
Home

BTW, as poucas respostas principais fazem uma mesclagem "superficial": se a mesma chave existe em obj1 e obj2, o valor em obj2 é mantido, o valor em obj1 é descartado. Por exemplo, se o exemplo da pergunta tivesse var obj2 = { animal: 'dog', food: 'bone' };, a mesclagem seria { food: 'bone', car: 'ford', animal: 'dog' }. Se você estiver trabalhando com "dados aninhados" e desejar uma "mesclagem profunda", procure respostas que mencionem "mesclagem profunda" ou "recursão". Se você possui valores arrays, use a opção "arrayMerge" do github "TehShrike / deepmerge", conforme mencionado aqui .
Página Inicial>

Siga o link abaixo stackoverflow.com/questions/171251/… O operador de spread, que é o novo recurso da versão ES6
Dev216

Respostas:


2908

Método Padrão ECMAScript 2018

Você usaria a propagação de objeto :

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

mergedagora é a união de obj1e obj2. As propriedades em obj2substituirão as que estão em 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};

Aqui também está a documentação MDN para esta sintaxe. Se você estiver usando o babel, precisará do plug-in babel-plugin-transform-object-rest-spread para que ele funcione.

Método Padrão 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);

(consulte Referência JavaScript MDN )


Método para ES5 e anteriores

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

Note que isso irá simplesmente adicionar todos os atributos de obj2que obj1o que pode não ser o que você quer se você ainda quiser usar o não modificada obj1.

Se você estiver usando uma estrutura que excreta todos os seus protótipos, será necessário ficar mais sofisticado com verificações como hasOwnProperty, mas esse código funcionará em 99% dos casos.

Exemplo de função:

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

90
Isso não funciona se os objetos tiverem os mesmos atributos de nome e você também desejar mesclar os atributos.
Xie Jìléi

69
Isso faz apenas uma cópia / mesclagem superficial. Tem o potencial de eliminar muitos elementos.
Jay Taylor

45
+1 por reconhecer que algumas almas pobres são forçadas a usar estruturas que
cagam

4
Isso não funcionou para mim por causa de "Portanto, ele atribui propriedades em vez de apenas copiar ou definir novas propriedades. Isso pode torná-lo inadequado para mesclar novas propriedades em um protótipo se as fontes de mesclagem contiverem getters". ( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… ). Eu tive que usar var merged = {...obj1, ...obj2}.
morgler

2
@ Ze'ev{...obj1, ...{animal: 'dog'}}
backslash1

1191

O jQuery também possui um utilitário para isso: http://api.jquery.com/jQuery.extend/ .

Retirado da documentação do 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" }

O código acima irá alterar o objeto existente nomeado settings.


Se você deseja criar um novo objeto sem modificar nenhum argumento, use este:

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.

166
Cuidado: a variável "configurações" será modificada. O jQuery não retorna uma nova instância. A razão para isso (e para a nomeação) é que .extend () foi desenvolvido para estender objetos, em vez de juntar coisas. Se você deseja um novo objeto (por exemplo, configurações são os padrões que você não deseja tocar), você sempre pode jQuery.extend ({}, configurações, opções);
Webmat

1
Pode apostar! Muito obrigado. Como o comentário anterior afirmou, ele é mal nomeado. Eu procurei documentos jQuery volta e quarto e não funcionou.
Mike Starov

28
Lembre-se, o jQuery.extend também possui uma configuração profunda (booleana). jQuery.extend(true,settings,override), o que é importante se uma propriedade nas configurações retiver um objeto e substituir apenas uma parte desse objeto. Em vez de remover as propriedades incomparáveis, a configuração profunda será atualizada apenas onde existir. O padrão é falso.
vol7ron

19
Nossa, eles realmente fizeram um bom trabalho nomeando isso. Se você pesquisar como estender um objeto Javascript, ele será exibido! Talvez você não o encontre se estiver tentando fundir ou costurar objetos Javascript.
Derek Greer

2
Não diga às pessoas que pensem em usar um método de biblioteca quando algumas linhas de JS vanilla fizerem o que quiserem. Houve um tempo antes do jQuery!
CrazyMerlin

348

O Harmony ECMAScript 2015 (ES6) especifica Object.assignquem fará isso.

Object.assign(obj1, obj2);

O suporte atual ao navegador está melhorando , mas se você estiver desenvolvendo para navegadores sem suporte, poderá usar um polyfill .


35
Note-se que esta é apenas uma mesclagem rasa
Joachim Lous

Eu acho que por sua vez Object.assigntem uma cobertura bastante decente: kangax.github.io/compat-table/es6/...
LazerBass

13
Agora, essa deve ser a resposta correta. Atualmente, as pessoas compilam seu código para serem compatíveis com o navegador raro (IE11) que não suporta esse tipo de coisa. Nota: se você não quiser adicionar obj2 para obj1, você pode retornar um novo objeto usandoObject.assign({}, obj1, obj2)
Duvrai

6
@Duvrai De acordo com relatos que eu vi, IE11 definitivamente não é rara em cerca de 18% da quota de mercado a partir de julho de 2016.
NanoWizard

3
@Kermani O OP aponta especificamente que a recursão é desnecessária. Portanto, embora seja útil saber que esta solução executa uma mesclagem superficial, essa resposta é suficiente. O comentário de JoachimLou recebeu votos bem merecidos e fornece essas informações extras quando necessário. Penso que a diferença entre a fusão profunda e superficial e tópicos semelhantes estão além do escopo desta discussão e são mais adequados para outras questões de SO.
NanoWizard 20/08

264

Pesquisei no código o código para mesclar propriedades do objeto e acabei aqui. No entanto, como não havia nenhum código para mesclagem recursiva, eu mesmo o escrevi. (Talvez o jQuery extend seja BTW recursivo?) De qualquer forma, espero que alguém ache isso útil também.

(Agora o código não 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;
}

Um exemplo

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

Produz o objeto o3 como

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

11
Bom, mas eu faria uma cópia profunda dos objetos primeiro. Dessa forma, o o1 também seria modificado, pois os objetos são passados ​​por referência.
Skerit

1
Era isso que eu estava procurando. Certifique-se de verificar se obj2.hasOwnProperty (p) antes de ir para a instrução try catch. Caso contrário, você pode acabar tendo outras porcarias sendo mescladas de cima na cadeia de protótipos.
Adam

3
Obrigado Markus. Observe que o1 também é modificado por MergeRecursive, portanto, no final, o1 e o3 são idênticos. Pode ser mais intuitivo se você não retornar nada, para que o usuário saiba que o que está fornecendo (o1) é modificado e se torna o resultado. Além disso, no seu exemplo, pode valer a pena adicionar algo ao o2 que não esteja originalmente no o1, para mostrar que as propriedades do o2 são inseridas no o1 (alguém abaixo submeteu um código alternativo que copia o seu exemplo, mas o deles quebra quando o o2 contém propriedades não no o1 - mas o seu trabalha nesse sentido).
Panqueca

7
Esta é uma boa resposta, mas há algumas queixas: 1) Nenhuma lógica deve ser baseada em exceções; nesse caso, qualquer exceção lançada será capturada com resultados imprevisíveis. 2) Concordo com o comentário da @ pancake sobre não retornar nada, pois o objeto original é modificado. Aqui está um exemplo jsFiddle sem a lógica de exceção: jsfiddle.net/jlowery2663/z8at6knn/4 - Jeff Lowery
Jeff Lowery

3
Além disso, obj2 [p] .constructor == Matriz é necessária em qualquer caso em que haja uma matriz de objetos.
O Compositor

175

Observe que underscore.js's- extendmethod faz isso em uma linha:

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

36
Este exemplo é bom, pois você está lidando com objetos anônimos, mas se esse não for o caso, o comentário de webmat na resposta do jQuery sobre aviso de mutações se aplica aqui, pois o sublinhado também altera o objeto de destino . Como a resposta jQuery, para fazer isso apenas merge livre de mutação em um objeto vazio:_({}).extend(obj1, obj2);
Abe Voelker

8
Há outro que pode ser relevante, dependendo do que você está tentando obter: _.defaults(object, *defaults)"Preencha as propriedades nulas e indefinidas no objeto com valores dos objetos padrão e retorne o objeto".
conny

Muitas pessoas comentaram sobre a modificação do objeto de destino, mas, em vez de fazer com que um método crie um novo, um padrão comum (no dojo, por exemplo) é dizer dojo.mixin (dojo.clone (myobj), {newpropertys: "to add para myobj "})
Colin D

7
O sublinhado pode levar um número arbitrário de objetos, para evitar a mutação do objeto original var mergedObj = _.extend({}, obj1, obj2).
lobati

84

Semelhante ao jQuery extend (), você tem a mesma função no 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);

1
@naXa Eu acho que seria uma opção se você não quiser adicionar uma lib apenas para este fn.
juanmf

67

Preciso mesclar objetos hoje, e essa pergunta (e respostas) me ajudou muito. Tentei algumas das respostas, mas nenhuma delas atendia às minhas necessidades, então combinei algumas, adicionei algo e criei uma nova função de mesclagem. Aqui 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;
};

Alguns exemplos de uso:

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

3
boa resposta, mas eu acho que se uma propriedade está presente no primeiro e no segundo e você está tentando fazer um novo objeto, fundindo primeiro e segundo, em seguida, os únicos presentes no primeiro objeto será perdido
Strikers

2
Mas não é um comportamento esperado? O objetivo desta função é substituir valores na ordem dos argumentos. O próximo substituirá a corrente. Como você pode preservar valores únicos? Do meu exemplo, do que você espera merge({}, t1, { key1: 1 })?
Emre Erkan

Minha expectativa é mesclar apenas valores sem tipo de objeto.
AGamePlayer 28/02

falha se o resultado esperado for o seguinte: gist.github.com/ceremcem/a54f90923c6e6ec42c7daf24cf2768bd
ceremcem

Isso funcionou para mim no modo estrito para o meu projeto node.js. Até agora, esta é a melhor resposta neste post.
klewis

48

Você pode usar propriedades de propagação de objeto - atualmente uma proposta de estágio 3 do ECMAScript.

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

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


Suporte ao navegador?
Alph.Dev

1
@ Alph.Dev Você conhece caniuse.com?
connexo

Não consigo fazer com que a sintaxe de três pontos funcione no node.js. nó reclama sobre isso.
klewis

41

As soluções fornecidas devem ser modificadas para verificar source.hasOwnProperty(property)os for..inloops antes da atribuição - caso contrário, você acaba copiando as propriedades de toda a cadeia de protótipos, o que raramente é desejado ...


40

Mesclar propriedades de N objetos em uma linha de código

Um Object.assignmétodo faz parte do padrão ECMAScript 2015 (ES6) e faz exatamente o que você precisa. ( IEnão suportado)

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

O método Object.assign () é usado para copiar os valores de todas as propriedades enumeráveis ​​de um ou mais objetos de origem para um objeto de destino.

Consulte Mais informação...

O polyfill para suportar navegadores mais antigos:

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

o que 'use strict' faz em browsers mais antigos ou por que está lá? navegadores que suportam o objeto [defineProperty; chaves; getOwnPropertyDescriptor; etc], não são exatamente antigos. São apenas versões anteriores dos gen.5 UAs.
precisa saber é o seguinte

Obrigado por esclarecer que Objects.assignretorna um objeto mesclado que as outras respostas não. Esse é um estilo de programação mais funcional.
Sridhar Sarnobat

36

Os dois seguintes são provavelmente um bom ponto de partida. O lodash também possui uma função de personalização para essas necessidades especiais!

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


4
Observe que os métodos acima modificam o objeto original.
Wtower 01/09/16

O método lodash funcionou para mim, pois eu queria mesclar dois objetos e reter propriedades aninhadas com mais de um ou dois níveis de profundidade (em vez de apenas substituí-los / limpá-los).
SamBrick

true @Wtower. Só tive um bug por causa disso. Melhor ter certeza que sempre começa como _.merge ({}, ...
Martin Cremer

29

A propósito, tudo o que você está fazendo é sobrescrever propriedades, não mesclar ...

É assim que a área de objetos JavaScript realmente é mesclada: somente as chaves no toobjeto que não são objetos serão sobrescritas from. Todo o resto será realmente mesclado . Obviamente, você pode alterar esse comportamento para não substituir nada que exista apenas se 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);

3
Ajudou-me muito, os js sublinhado estender fez realmente de substituição, em vez de merge
David Cumps

1
Vale ressaltar que typeof array e typeof null retornarão 'object' e quebrarão isso.
Salakar 19/07/2016

@ u01jmg3 ver a minha resposta aqui: stackoverflow.com/questions/27936772/... - a função IsObject
Salakar

Isso quebra se você usar é no modo estrito para projetos Node.js
klewis

28

Aqui está minha facada que

  1. Suporta mesclagem profunda
  2. Não modifica argumentos
  3. Leva qualquer número de argumentos
  4. Não estende o protótipo de objeto
  5. Não depende de outra biblioteca ( jQuery , MooTools , Underscore.js , etc.)
  6. Inclui verificação para hasOwnProperty
  7. É curto :)

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

Exemplo:

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

Comparada com as outras que testei nesta página, essa função é verdadeiramente recursiva (faz uma mesclagem profunda) e imita o que o jQuery.extend () realmente faz bem. No entanto, seria melhor modificar o primeiro objeto / argumento para que um usuário possa decidir se deseja passar um objeto vazio {}como o primeiro parâmetro ou se a função modificar o objeto original. Portanto, se você mudar dst = {}para dst = arguments[0]ele vai mudar o primeiro objeto que você passa para o objeto mesclado
Jasdeep Khalsa

3
Agora parou de funcionar no chrome. Eu acho que é uma má idéia usar essa construção toString.call(src) == '[object Object]'para verificar se há um objeto ou não. typeof srcé muito melhor. Além disso, transforma Arrays em objeto (pelo menos, eu tenho esse comportamento no Chrome mais recente).
M03geek 21/05

Eu precisava fazer isso dentro de um loop ganancioso, comparei essa função com a função que eu estava usando até agora _.merge (object, [sources]) do lodash (uma espécie de lib de sublinhado): sua função é quase duas vezes mais rápida obrigado companheiro.
Arnaud Bouchot 11/11

20

Object.assign ()

ECMAScript 2015 (ES6)

Esta é uma nova tecnologia, parte do padrão ECMAScript 2015 (ES6). A especificação dessa tecnologia foi finalizada, mas verifique a tabela de compatibilidade quanto ao status de uso e implementação em vários navegadores.

O método Object.assign () é usado para copiar os valores de todas as propriedades enumeráveis ​​de um ou mais objetos de origem para um objeto de destino. Ele retornará o 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.

19

Para objetos não muito complicados, você pode 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_/

Lembre-se de que neste exemplo "} {" não deve ocorrer dentro de uma string!


4
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 #

ou como one-liner:objMerge = JSON.parse((JSON.stringify(obj1) + JSON.stringify(obj2)).replace(/\}\{/, ", "));
Rabino Shuki Gur

1
@ Charles, desculpe, mas como é "este método muito perigoso", mas também divertido - este é o mais seguro possível. Primeiro, ele usa o JSON para fazer o trabalho pesado de ler o conteúdo do objeto; em segundo lugar, ele usa o objeto JSON, que é o mais seguro máquina otimizada para revisar o JSO a partir de uma literal de sequência de objetos compatível com JSON. E também é compatível com o IE8, que é o primeiro navegador a implementar seu padrão. Isso me faz pensar "por que, oh, por que você disse uma coisa tão estúpida e se safou"?
precisa saber é o seguinte

Acredito que as funções do objeto sejam quebradas aqui, pois o stringify não pode ser usado, mas podemos esclarecer outras desvantagens?
yeahdixon

18

Há uma biblioteca chamada deepmergeno GitHub : Isso parece estar ficando alguma tração. É autônomo, disponível através dos gerenciadores de pacotes npm e bower.

Eu estaria inclinado a usar ou melhorar isso em vez de copiar e colar o código das respostas.


17

A melhor maneira de fazer isso é adicionar uma propriedade adequada que não seja enumerável usando Object.defineProperty.

Dessa forma, você ainda poderá iterar nas propriedades dos objetos sem ter o "estender" recém-criado que você obteria se criasse a propriedade com Object.prototype.extend.

Espero que isso ajude:

Object.defineProperty (Object.prototype, "extend", {
    enumerável: false,
    value: function (from) {
        var props = Object.getOwnPropertyNames (from);
        var dest = isto;
        props.forEach (function (name) {
            if (nome no destino) {
                var destination = Object.getOwnPropertyDescriptor (de, nome);
                Object.defineProperty (destino, nome, destino);
            }
        });
        devolva isso;
    }
});

Depois de ter esse trabalho, você pode fazer:

var obj = {
    nome: 'pilha',
    acabamento: 'estouro'
}
substituição de var = {
    nome: 'estoque'
};

obj.extend (substituição);

Acabei de escrever um post sobre o assunto aqui: http://onemoredigit.com/post/1527191998/extending-objects-in-node-js


Espere um segundo --- o código não funciona! :( o inseriu no jsperf para comparar com outros algoritmos de mesclagem, e o código falha - a extensão da função não existe
Jens Roland

16

Você pode simplesmente usar o jQuery extend

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

jQuery.extend(obj1, obj2);

Agora obj1contém todos os valores de obj1eobj2


2
obj1 conterá todos os valores, não obj2. Você está estendendo o primeiro objeto. jQuery.extend( target [, object1 ] [, objectN ] )
ruuter

5
Esta pergunta não é sobre jQuery. JavaScript! = JQuery
Jorge Riv

1
Eu Googled para JavaScript, mas esta é uma abordagem mais simples
Ehsan

13

O protótipo possui:

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

obj1.extend(obj2) fará o que você quiser.


6
Você quis dizer Object.prototype.extend? De qualquer forma, não é uma boa ideia estender o Object.prototype. -1.
SolutionYogi

Venha para pensar sobre isso, provavelmente deveria ser Object.prototypee não Object... Boa ideia ou não, é isso que a prototype.jsestrutura faz e, portanto, se você a estiver usando ou outra estrutura que depende dela, Object.prototypeela já será estendida extend.
ephemient

2
Não pretendo falar com você, mas dizer que está tudo bem porque prototype.js é um argumento ruim. O Prototype.js é popular, mas não significa que eles sejam o modelo de como fazer JavaScript. Verifique esta revisão detalhada da biblioteca Prototype por um profissional JS. dhtmlkitchen.com/?category=/JavaScript/&date=2008/06/17/…
SolutionYogi

7
Quem se importa se está certo ou errado? Se funcionar, então funciona. Esperar a solução perfeita é excelente, exceto quando se tenta manter um contrato, conquistar novos clientes ou cumprir prazos. Dê a quem aqui é apaixonado por programar dinheiro grátis e eles eventualmente farão tudo da maneira "certa".
David

1
Onde vocês encontraram Object.prototype? Object.extendnão interfere com seus objetos, apenas anexa uma função a um Objectobjeto. E obj1.extend(obj2)não é o caminho correto para a função de fogo, uso Object.extend(obj1, obj2)insted
artnikpro

13

** A fusão de objetos é simples de usar Object.assignou o ...operador spread **

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

Os objetos são mesclados da direita para a esquerda, isso significa que os objetos que possuem propriedades idênticas aos objetos à direita serão substituídos.

Neste exemplo, obj2.carsubstituiobj1.car


12

Apenas se alguém estiver usando a Google Closure Library :

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

Função auxiliar semelhante existe para a 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'

10

Estendi o método de David Coallier:

  • Adicionada a possibilidade de mesclar vários objetos
  • Suporta objetos profundos
  • substituir parâmetro (que é detectado se o último parâmetro for um booleano)

Se a substituição for falsa, nenhuma propriedade será substituída, mas novas propriedades serão adicionadas.

Uso: obj.merge (mescla ... [, substitui]);

Aqui está o meu 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;
    }
});

Exemplos e casos de teste:

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

Meu método equals pode ser encontrado aqui: Comparação de objetos em JavaScript



9

No Ext JS 4, isso pode ser feito da seguinte maneira:

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

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

Veja mesclagem (objeto): Objeto .


1
Cuidado com EXTJS-9173 em EXT.Object.merge bugs ainda após +2 anos ainda não resolvido
Chris

9

Uau .. este é o primeiro post StackOverflow que eu já vi com várias páginas. Desculpas por adicionar outra "resposta"

Este método é para o ES5 e versões anteriores - existem muitas outras respostas para o ES6.

Não vi nenhum objeto "profundo" mesclado utilizando a argumentspropriedade Aqui está a minha resposta - compacta e recursiva , permitindo que argumentos de objetos ilimitados sejam passados:

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

A parte comentada é opcional. Ela simplesmente ignorará os argumentos passados ​​que não são objetos (evitando erros).

Exemplo:

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

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

Esta resposta é agora apenas uma gota no oceano ...


A resposta mais curta que funciona muito bem! Merece mais votos!
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 () - Link

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

Usando _.merge () - Link

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

Usando _.extend () - Link

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

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

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

Saída 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"}

7

Com base nas respostas de Markus e vsync , esta é uma versão expandida. A função aceita qualquer número de argumentos. Ele pode ser usado para definir propriedades nos nós DOM e fazer cópias profundas dos valores. No entanto, o primeiro argumento é dado por referência.

Para detectar um nó DOM, a função isDOMNode () é usada (consulte a pergunta sobre estouro de pilha JavaScript isDOM - Como você verifica se um objeto JavaScript é um objeto DOM? )

Foi testado no Opera 11, Firefox 6, Internet Explorer 8 e 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;
}

Alguns exemplos

Definir innerHTML e estilo de um elemento HTML

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

Mesclar matrizes e objetos. Observe que undefined pode ser usado para preservar valores na matriz / objeto à esquerda.

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

Qualquer argumento que não seja um objeto JavaScript (incluindo nulo) será ignorado. Exceto pelo primeiro argumento, também os nós DOM são descartados. Cuidado, isto é, seqüências de caracteres, criadas como novas String (), são de fato 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'}

Se você deseja mesclar dois objetos em um novo (sem afetar nenhum dos dois), forneça {} como primeiro 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 mesclar também matrizes

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


5

Você deve usar os padrões do lodash

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

5

Com o Underscore.js , para mesclar uma matriz de objetos, faça:

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

Isso resulta em:

Object {a: 1, b: 2, c: 3, d: 4}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.