Lodash - разница между .extend () / .assign () и .merge ()

Ответы:

584

Вот как extend/ assignработает: Для каждого свойства в источнике скопируйте его значение как есть в место назначения. если значения свойств сами по себе являются объектами, рекурсивного обхода их свойств не происходит. Весь объект будет взят из источника и установлен в место назначения.

Вот как это mergeработает: Для каждого свойства в источнике проверьте, является ли это свойство самим объектом. Если это так, то идите рекурсивно вниз и попытайтесь отобразить свойства дочерних объектов от источника к месту назначения. По сути, мы объединяем иерархию объектов от источника к месту назначения. В то время как для extend/ assignэто простая одноуровневая копия свойств из источника в место назначения.

Вот простой JSBin, который прояснил бы это: http://jsbin.com/uXaqIMa/2/edit?js,console

Вот более сложная версия, которая также включает в себя массив: http://jsbin.com/uXaqIMa/1/edit?js,console

Шиталь шах
источник
16
Важным отличием, по-видимому, является то, что, хотя _.merge возвращает новый объединенный объект, _.extend изменяет целевой объект на месте,
letronje
70
Похоже, они оба видоизменяют целевой объект независимо от того, что они возвращают.
Джейсон Райс
7
Также кажется, что _.extend закрывает члены целевого объекта, если они отсутствуют в исходном объекте, что меня удивляет.
Джейсон Райс
5
@JasonRice Они не забиты. Например, в этой скрипте свойство "a" не сжимается . Это правда, что после расширения dest ["p"] ["y"] больше не существует. Это связано с тем, что до расширения src и dest оба имели свойство "p", поэтому свойство "p" dest полностью перезаписывалось свойством src "p" (теперь они точно такие же).
Кевин Уилер
14
Чтобы было понятно, оба метода модифицируют / перезаписывают первый аргумент по ссылке. Поэтому, если вы хотите получить новый объект в результате слияния, лучше всего передать литерал объекта. var combined = merge({}, src, dest)
Джон Жак
535

Lodash версия 3.10.1

Методы по сравнению

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

сходства

  • Ни один из них не работает с массивами, как вы могли ожидать
  • _.extendэто псевдоним для _.assign, поэтому они идентичны
  • Кажется, что все они модифицируют целевой объект (первый аргумент)
  • Все они обращаются nullодинаково

Различия

  • _.defaultsи _.defaultsDeepобрабатывает аргументы в обратном порядке по сравнению с другими (хотя первый аргумент все еще является целевым объектом)
  • _.merge а также _.defaultsDeep объединит дочерние объекты, а остальные перезапишут на корневом уровне
  • Только _.assign и _.extendперезапишет значение сundefined

тесты

Все они обрабатывают членов в корне похожими способами.

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

_.assign ручки undefined но другие пропустят это

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

Они все справляются null одинаково

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

Но только _.merge и _.defaultsDeepбудут сливать дочерние объекты

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

И, похоже, никто из них не объединит массивы

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

Все модифицируют целевой объект

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

Ни один из них не работает должным образом на массивах

Примечание. Как указывает @Mistic, Lodash рассматривает массивы как объекты, ключи которых являются индексом в массиве.

_.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
источник
32
Он фактически объединяет массивы точно так же, как объединяет объекты, потому что массивы - это объекты с числовыми ключами. Я согласен, что можно ожидать объединения или замены массивов, в зависимости от использования.
Мистик
11
Отличный ответ. Тесты были очень дидактическими :-)
Lucio Paiva
5
_.extend is an alias for _.assign, so they are identicalконфликтует сOnly _.assign will overwrite a value with undefined
Chazt3n
9
Начиная с версии 4.0, _.extend теперь является псевдонимом для _.assignIn, а не _assign. Функция assignIn добавляет работу с унаследованными свойствами.
Майк Хедман
2
Нулевой трактуется так же, как неопределенный здесь?
C_B
75

Еще одно отличие, на которое следует обратить внимание, это обработка undefinedзначений:

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

Так mergeчто не будет объединять undefinedзначения в определенные значения.

samz
источник
3
Это только я или это делает lodash.extend совершенно бесполезным в том смысле, что он всегда возвращает клон объекта toMerge?
Джейсон Райс
6
Если бы у mergeIntoнего были свойства, которых у toMergeнего не было, он бы сохранил эти свойства. В этом случае это не был бы клон.
Дэвид Нил
1
@JasonRice удалите пустой {} и он объединит его вместо lodash.merge (mergeInto, toMerge)
Сидональдсон
20

Также может быть полезно рассмотреть то, что они делают с семантической точки зрения:

_.assign

   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.

Я считаю, что изучение этих методов с семантической точки зрения позволит вам лучше «угадать», каково будет поведение для всех различных сценариев существующих и несуществующих ценностей.

epeleg
источник
3

Если вы хотите глубокое копирование без переопределения при сохранении той же objссылки

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

mbao01
источник