Lodash - różnica między .extend () / .assign () i .merge ()

Odpowiedzi:

582

Oto jak extend/ assigndziała: Dla każdej właściwości w źródle skopiuj jej wartość „jak jest” do miejsca docelowego. jeśli same wartości właściwości są obiektami, nie następuje rekurencyjne przechodzenie ich właściwości. Cały obiekt zostanie pobrany ze źródła i ustawiony w miejscu docelowym.

Oto jak mergedziała: Dla każdej właściwości w źródle sprawdź, czy ta właściwość jest samym obiektem. Jeśli tak, to rekurencyjnie zejdź w dół i spróbuj zmapować właściwości obiektu potomnego ze źródła do miejsca docelowego. Zasadniczo łączymy hierarchię obiektów od źródła do miejsca docelowego. W przypadku extend/ assignjest to prosta, jednopoziomowa kopia właściwości od źródła do miejsca przeznaczenia.

Oto prosty JSBin, który uczyni to krystalicznie czystym: http://jsbin.com/uXaqIMa/2/edit?js,console

Oto bardziej rozbudowana wersja, która zawiera również tablicę w tym przykładzie: http://jsbin.com/uXaqIMa/1/edit?js,console

Shital Shah
źródło
16
Ważna różnica wydaje się polegać na tym, że podczas gdy _.merge zwraca nowy scalony obiekt, _.extend mutuje obiekt docelowy w miejscu,
letronje
69
Oba wydają się mutować obiekt docelowy niezależnie od tego, co zwracają.
Jason Rice,
7
Wygląda również na to, że _.extend blokuje członków obiektu docelowego, jeśli nie są obecne w obiekcie źródłowym, co mnie zaskakuje.
Jason Rice,
5
@JasonRice Nie zapychają się. Na przykład w tym skrzypce właściwość „a” nie zostaje zablokowana . Prawdą jest, że po rozszerzeniu dest ["p"] ["y"] już nie istnieje - Jest tak, ponieważ przed rozszerzeniem src i dest oba miały właściwość „p”, więc właściwość dest „p” zostaje całkowicie nadpisana przez właściwość src „p” (są teraz dokładnie tym samym obiektem).
Kevin Wheeler,
14
Dla jasności obie metody modyfikują / zastępują pierwszy argument przez odwołanie. Więc jeśli chcesz nowy obiekt z wynikowego scalenia, najlepiej przekazać literał obiektu. var combined = merge({}, src, dest)
Jon Jaques,
534

Wersja Lodash 3.10.1

Porównywane metody

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

Podobieństwa

  • Żadne z nich nie działa na tablicach, jak można się spodziewać
  • _.extendjest pseudonimem _.assign, więc są identyczne
  • Wszystkie wydają się modyfikować obiekt docelowy (pierwszy argument)
  • Wszystkie obsługują nullto samo

Różnice

  • _.defaultsi _.defaultsDeepprzetwarza argumenty w odwrotnej kolejności niż inne (chociaż pierwszy argument jest nadal obiektem docelowym)
  • _.mergei _.defaultsDeeppołączy obiekty potomne, a pozostałe nadpiszą na poziomie głównym
  • Tylko _.assigni _.extendnadpisze wartośćundefined

Testy

Wszystkie obsługują członków u podstawy w podobny sposób.

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

_.assignuchwyty, undefinedale inni to pominą

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

Wszystkie działają nulltak samo

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

Ale tylko _.mergei _.defaultsDeeppołączy obiekty potomne

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

I wydaje się, że żadne z nich nie połączy tablic

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

Wszystkie modyfikują obiekt docelowy

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

Żaden tak naprawdę nie działa zgodnie z oczekiwaniami na tablicach

Uwaga: Jak wskazał @Mistic, Lodash traktuje tablice jako obiekty, w których klucze są indeksem tablicy.

_.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
źródło
31
W rzeczywistości łączy tablice dokładnie tak, jak scala obiekty, ponieważ tablice są obiektami z klawiszami numerycznymi. Zgadzam się, że należałoby oczekiwać połączenia lub zastąpienia tablic, zależy od użycia.
Mistic
11
Doskonała odpowiedź. Testy były bardzo dydaktyczne :-)
Lucio Paiva,
5
_.extend is an alias for _.assign, so they are identicalwchodzi w konflikt zOnly _.assign will overwrite a value with undefined
Chazt3n
9
Począwszy od wersji 4.0, _.extend jest teraz aliasem dla _.assignIn, a nie _assign. Funkcja assignIn dodaje obsługę odziedziczonych właściwości.
Mike Hedman
2
czy wartość null jest traktowana tak samo jak niezdefiniowana tutaj?
C_B,
75

Inną różnicą, na którą należy zwrócić uwagę, jest obsługa undefinedwartości:

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

Więc mergenie scali undefinedwartości w zdefiniowane wartości.

samz
źródło
3
Czy to tylko ja, czy czyni to lodash.extend całkowicie bezużytecznym, ponieważ zawsze zwraca klon obiektu „toMerge”?
Jason Rice,
6
Gdyby mergeIntomiał właściwości, toMergektórych nie miał, zachowałby te właściwości. W takim przypadku nie byłby to klon.
David Neale
1
@JasonRice usuń pusty {}, a scali go w miejscu lodash.merge (mergeInto, toMerge)
sidonaldson
20

Pomocne może być również rozważenie tego, co robią z semantycznego punktu widzenia:

_.przydzielać

   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)

_.łączyć

   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.

Wierzę, że nauka myślenia o tych metodach z semantycznego punktu widzenia pozwoliłaby lepiej „odgadnąć”, jakie byłoby zachowanie dla wszystkich różnych scenariuszy wartości istniejących i nieistniejących.

epeleg
źródło
3

Jeśli chcesz głębokiej kopii bez przesłonięcia, zachowując to samo objodniesienie

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

mbao01
źródło