Lodash - diferença entre .extend () / .assign () e .merge ()

Respostas:

584

Veja como extend/assign obras: Para cada propriedade na origem, copie seu valor como está até o destino. se os próprios valores da propriedade são objetos, não há um percurso recursivo de suas propriedades. O objeto inteiro seria retirado da origem e definido no destino.

Eis como mergefunciona: Para cada propriedade na fonte, verifique se essa propriedade é o próprio objeto. Se estiver, desça recursivamente e tente mapear as propriedades do objeto filho da origem ao destino. Então, basicamente, mesclamos a hierarquia de objetos da origem ao destino. Enquanto para extend/ assign, é uma cópia simples de um nível das propriedades da origem ao destino.

Aqui está o JSBin simples que deixaria isso bem claro: http://jsbin.com/uXaqIMa/2/edit?js,console

Aqui está uma versão mais elaborada que inclui matriz no exemplo também: http://jsbin.com/uXaqIMa/1/edit?js,console

Shital Shah
fonte
16
Uma diferença importante parece ser que, enquanto _.merge retorna uma nova mesclado objeto, sofre mutações _.extend o objeto de destino no local,
letronje
70
Os dois parecem mudar o objeto de destino, independentemente do que retornarem.
Jason Rice
7
Parece também que _.extend derruba membros do objeto de destino se eles não estiverem presentes no objeto de origem, o que é surpreendente para mim.
Jason Rice
5
@JasonRice Eles não são derrotados. Por exemplo, neste violino, a propriedade "a" não é derrotada . É verdade que, após a extensão, dest ["p"] ["y"] não existe mais - Isso ocorre porque antes da extensão src e dest ambos possuíam uma propriedade "p", portanto, a propriedade "p" do dest é completamente substituída pela propriedade "p" do src (eles são exatamente o mesmo objeto agora).
Kevin Wheeler
14
Para ser claro, ambos os métodos modificam / substituem o primeiro argumento por referência. Portanto, se você deseja um novo objeto a partir da mesclagem resultante, é melhor passar um literal de objeto. var combined = merge({}, src, dest)
Jon Jaques
535

Versão Lodash 3.10.1

Métodos comparados

  • _.merge(object, [sources], [customizer], [thisArg])
  • _.assign(object, [sources], [customizer], [thisArg])
  • _.extend(object, [sources], [customizer], [thisArg])
  • _.defaults(object, [sources])
  • _.defaultsDeep(object, [sources])

Semelhanças

  • Nenhum deles funciona em matrizes como você poderia esperar
  • _.extend é um apelido para _.assign , então eles são idênticos
  • Todos eles parecem modificar o objeto de destino (primeiro argumento)
  • Todos eles lidam com nullo mesmo

Diferenças

  • _.defaults e _.defaultsDeep processa os argumentos em ordem inversa em comparação com os outros (embora o primeiro argumento ainda seja o objeto de destino)
  • _.merge e _.defaultsDeep mesclará objetos filho e os outros substituirão no nível raiz
  • Somente _.assigne _.extendsubstituirá um valor comundefined

Testes

Todos eles lidam com membros na raiz de maneiras semelhantes.

_.assign      ({}, { a: 'a' }, { a: 'bb' }) // => { a: "bb" }
_.merge       ({}, { a: 'a' }, { a: 'bb' }) // => { a: "bb" }
_.defaults    ({}, { a: 'a' }, { a: 'bb' }) // => { a: "a"  }
_.defaultsDeep({}, { a: 'a' }, { a: 'bb' }) // => { a: "a"  }

_.assignalças, undefinedmas os outros vão pular

_.assign      ({}, { a: 'a'  }, { a: undefined }) // => { a: undefined }
_.merge       ({}, { a: 'a'  }, { a: undefined }) // => { a: "a" }
_.defaults    ({}, { a: undefined }, { a: 'bb' }) // => { a: "bb" }
_.defaultsDeep({}, { a: undefined }, { a: 'bb' }) // => { a: "bb" }

Todos eles lidam com nullo mesmo

_.assign      ({}, { a: 'a'  }, { a: null }) // => { a: null }
_.merge       ({}, { a: 'a'  }, { a: null }) // => { a: null }
_.defaults    ({}, { a: null }, { a: 'bb' }) // => { a: null }
_.defaultsDeep({}, { a: null }, { a: 'bb' }) // => { a: null }

Mas apenas _.mergee _.defaultsDeepmesclará objetos filho

_.assign      ({}, {a:{a:'a'}}, {a:{b:'bb'}}) // => { "a": { "b": "bb" }}
_.merge       ({}, {a:{a:'a'}}, {a:{b:'bb'}}) // => { "a": { "a": "a", "b": "bb" }}
_.defaults    ({}, {a:{a:'a'}}, {a:{b:'bb'}}) // => { "a": { "a": "a" }}
_.defaultsDeep({}, {a:{a:'a'}}, {a:{b:'bb'}}) // => { "a": { "a": "a", "b": "bb" }}

E nenhum deles irá mesclar matrizes parece

_.assign      ({}, {a:['a']}, {a:['bb']}) // => { "a": [ "bb" ] }
_.merge       ({}, {a:['a']}, {a:['bb']}) // => { "a": [ "bb" ] }
_.defaults    ({}, {a:['a']}, {a:['bb']}) // => { "a": [ "a"  ] }
_.defaultsDeep({}, {a:['a']}, {a:['bb']}) // => { "a": [ "a"  ] }

Todos modificam o objeto de destino

a={a:'a'}; _.assign      (a, {b:'bb'}); // a => { a: "a", b: "bb" }
a={a:'a'}; _.merge       (a, {b:'bb'}); // a => { a: "a", b: "bb" }
a={a:'a'}; _.defaults    (a, {b:'bb'}); // a => { a: "a", b: "bb" }
a={a:'a'}; _.defaultsDeep(a, {b:'bb'}); // a => { a: "a", b: "bb" }

Nenhum realmente funciona como esperado em matrizes

Nota: Como o @Mistic apontou, o Lodash trata matrizes como objetos em que as chaves são o índice da matriz.

_.assign      ([], ['a'], ['bb']) // => [ "bb" ]
_.merge       ([], ['a'], ['bb']) // => [ "bb" ]
_.defaults    ([], ['a'], ['bb']) // => [ "a"  ]
_.defaultsDeep([], ['a'], ['bb']) // => [ "a"  ]

_.assign      ([], ['a','b'], ['bb']) // => [ "bb", "b" ]
_.merge       ([], ['a','b'], ['bb']) // => [ "bb", "b" ]
_.defaults    ([], ['a','b'], ['bb']) // => [ "a", "b"  ]
_.defaultsDeep([], ['a','b'], ['bb']) // => [ "a", "b"  ]
Nate
fonte
32
Na verdade, mescla matrizes exatamente como mescla objetos, porque matrizes são objetos com teclas numéricas. Concordo que seria de esperar concatenar ou substituir matrizes, depende do uso.
Mistic
11
Excelente resposta. Os testes foram muito didáticos :-)
Lucio Paiva
5
_.extend is an alias for _.assign, so they are identicalconflitos comOnly _.assign will overwrite a value with undefined
Chazt3n
9
A partir da v4.0, _.extend agora é um alias para _.assignIn, não _assign. A função assignIn adiciona ao lidar com propriedades herdadas.
Mike Hedman
2
nulo é tratado da mesma forma que não definido aqui?
C_B
75

Outra diferença a ser observada é a manipulação de undefinedvalores:

mergeInto = { a: 1}
toMerge = {a : undefined, b:undefined}
lodash.extend({}, mergeInto, toMerge) // => {a: undefined, b:undefined}
lodash.merge({}, mergeInto, toMerge)  // => {a: 1, b:undefined}

Portanto merge, não mesclará undefinedvalores em valores definidos.

samz
fonte
3
Sou eu ou isso faz com que o lodash.extend seja completamente inútil, pois sempre retorna um clone do objeto 'toMerge'?
Jason Rice
6
Se mergeIntotivesse propriedades que toMergenão possuíam, ele as manteria. Nesse caso, não seria um clone.
David Neale
1
@JasonRice remover o vazio {} e vai fundir-lo no lugar lodash.merge (mergeInto, toMerge)
sidonaldson
20

Também pode ser útil considerar o que eles fazem do ponto de vista semântico:

_.atribuir

   will assign the values of the properties of its second parameter and so on,
   as properties with the same name of the first parameter. (shallow copy & override)

_.merge

   merge is like assign but does not assign objects but replicates them instead.
  (deep copy)

_.defaults

   provides default values for missing values.
   so will assign only values for keys that do not exist yet in the source.

_.defaultsDeep

   works like _defaults but like merge will not simply copy objects
   and will use recursion instead.

Acredito que aprender a pensar nesses métodos do ponto de vista semântico permitiria "adivinhar" qual seria o comportamento de todos os diferentes cenários de valores existentes e não existentes.

epeleg
fonte
3

Se você deseja uma cópia profunda sem substituir, mantendo a mesma objreferência

obj = _.assign(obj, _.merge(obj, [source]))

mbao01
fonte