Lodash - différence entre .extend () / .assign () et .merge ()

Réponses:

584

Voici comment extend/ assignfonctionne: Pour chaque propriété dans la source, copiez sa valeur telle quelle dans la destination. si les valeurs de propriété elles-mêmes sont des objets, il n'y a pas de traversée récursive de leurs propriétés. L'objet entier serait pris de la source et placé dans la destination.

Voici comment cela mergefonctionne: Pour chaque propriété dans la source, vérifiez si cette propriété est un objet lui-même. Si c'est le cas, descendez récursivement et essayez de mapper les propriétés des objets enfants de la source à la destination. Donc, nous fusionnons essentiellement la hiérarchie des objets de la source à la destination. Tandis que pour extend/ assign, c'est une simple copie à un niveau des propriétés de la source à la destination.

Voici un JSBin simple qui rendrait ce cristal clair: http://jsbin.com/uXaqIMa/2/edit?js,console

Voici une version plus élaborée qui inclut également un tableau dans l'exemple: http://jsbin.com/uXaqIMa/1/edit?js,console

Shital Shah
la source
16
Une différence importante semble être que tandis que _.merge renvoie un nouvel objet fusionné, _.extend mute l'objet de destination en place,
letronje
70
Ils semblent tous les deux muter l'objet de destination indépendamment de ce qu'ils retournent.
Jason Rice
7
Il semble également que _.extend frappe les membres de l'objet de destination s'ils ne sont pas présents dans l'objet source, ce qui me surprend.
Jason Rice
5
@JasonRice Ils ne sont pas encombrés. Par exemple, dans ce violon, la propriété "a" n'est pas encombrée . Il est vrai qu'après l'extension, dest ["p"] ["y"] n'existe plus - C'est parce qu'avant l'extension src et dest avaient tous deux une propriété "p", donc la propriété "p" de dest est complètement écrasée par la propriété "p" de src (ils sont exactement le même objet maintenant).
Kevin Wheeler
14
Pour être clair, les deux méthodes modifient / écrasent le premier argument par référence. Donc, si vous voulez un nouvel objet de la fusion résultante, mieux vaut passer un objet littéral. var combined = merge({}, src, dest)
Jon Jaques
535

Lodash version 3.10.1

Méthodes comparées

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

Similitudes

  • Aucun d'entre eux ne fonctionne sur les tableaux comme vous pouvez vous y attendre
  • _.extendest un alias pour _.assign, ils sont donc identiques
  • Tous semblent modifier l'objet cible (premier argument)
  • Tous gèrent nullla même chose

Différences

  • _.defaultset _.defaultsDeeptraite les arguments dans l'ordre inverse par rapport aux autres (bien que le premier argument soit toujours l'objet cible)
  • _.mergeet _.defaultsDeepfusionnera les objets enfants et les autres écraseront au niveau racine
  • Seulement _.assignet _.extendécrasera une valeur avecundefined

Les tests

Ils gèrent tous les membres à la racine de manière similaire.

_.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"  }

_.assignpoignées undefinedmais les autres l'ignoreront

_.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" }

Ils gèrent tous nullla même chose

_.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 }

Mais seulement _.mergeet _.defaultsDeepfusionnera les objets enfants

_.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" }}

Et aucun d'eux ne fusionnera les tableaux, il semble

_.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"  ] }

Tous modifient l'objet cible

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

Aucun ne fonctionne vraiment comme prévu sur les tableaux

Remarque: Comme l'a souligné @Mistic, Lodash traite les tableaux comme des objets dont les clés sont l'index dans le tableau.

_.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
la source
32
Il fusionne en fait des tableaux exactement comme il fusionne des objets, car les tableaux sont des objets avec des touches numériques. Je suis d'accord que l'on s'attendrait à concaténer ou à remplacer des tableaux, selon l'utilisation.
Mistic
11
Excellente réponse. Les tests ont été très didactiques :-)
Lucio Paiva
5
_.extend is an alias for _.assign, so they are identicalconflits avecOnly _.assign will overwrite a value with undefined
Chazt3n
9
Depuis la version 4.0, _.extend est désormais un alias pour _.assignIn, et non _assign. La fonction assignIn ajoute le traitement des propriétés héritées.
Mike Hedman
2
null est-il traité de la même manière que non défini ici?
C_B
75

Une autre différence à prendre en compte est la gestion des undefinedvaleurs:

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

mergeNe fusionnera donc pas les undefinedvaleurs en valeurs définies.

samz
la source
3
Est-ce juste moi ou cela rend-il lodash.extend complètement inutile en ce qu'il retourne toujours un clone de l'objet 'toMerge'?
Jason Rice
6
S'il mergeIntoavait des propriétés qui toMergen'en avaient pas, il conserverait ces propriétés. Dans ce cas, ce ne serait pas un clone.
David Neale
1
@JasonRice supprimez le {} vide et il le fusionnera en place lodash.merge (mergeInto, toMerge)
sidonaldson
20

Il pourrait également être utile de considérer ce qu'ils font d'un point de vue sémantique:

_.attribuer

   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)

_.fusionner

   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.

Je crois qu'apprendre à penser à ces méthodes du point de vue sémantique vous permettrait de mieux "deviner" quel serait le comportement pour tous les différents scénarios de valeurs existantes et non existantes.

epeleg
la source
3

Si vous souhaitez une copie complète sans remplacement tout en conservant la même objréférence

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

mbao01
la source