Lodash - .extend() / .assign() 和 .merge() 之间的区别

Posted

技术标签:

【中文标题】Lodash - .extend() / .assign() 和 .merge() 之间的区别【英文标题】:Lodash - difference between .extend() / .assign() and .merge() 【发布时间】:2013-11-26 18:23:32 【问题描述】:

在Lodash 库中,有人可以对merge 和extend / assign 提供更好的解释。

这是一个简单的问题,但答案却让我回避了。

【问题讨论】:

【参考方案1】:

如果您想要一个没有覆盖的深层副本,同时保留相同的 obj 参考

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

【讨论】:

【参考方案2】:

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

【讨论】:

它实际上合并数组就像它合并对象一样,因为数组是带有数字键的对象。我同意人们期望连接或替换数组,这取决于使用情况。 优秀的答案。测试非常具有指导意义:-) _.extend is an alias for _.assign, so they are identicalOnly _.assign will overwrite a value with undefined 冲突 从 v4.0 开始,_.extend 现在是 _.assignIn 的别名,而不是 _assign。 assignIn 函数增加了对继承属性的处理。 这里的 null 是否与未定义相同?【参考方案3】:

从语义的角度考虑它们的作用也可能会有所帮助:

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

我相信,学习从语义的角度思考这些方法可以让您更好地“猜测”现有和非现有值的所有不同场景的行为。

【讨论】:

【参考方案4】:

extend/assign 的工作原理如下:对于源中的每个属性,将其值原样复制到目标。如果属性值本身是对象,则不会递归遍历它们的属性。整个对象将从源获取并设置到目标。

merge 的工作原理如下:对于源中的每个属性,检查该属性是否是对象本身。如果是,则递归下去并尝试将子对象属性从源映射到目标。所以本质上我们将对象层次结构从源合并到目标。而对于extend/assign,它是从源到目标的简单的一级属性副本。

这里有一个简单的 JSBin,可以让这一切变得一清二楚: http://jsbin.com/uXaqIMa/2/edit?js,console

下面是更详细的版本,示例中也包含数组: http://jsbin.com/uXaqIMa/1/edit?js,console

【讨论】:

一个重要的区别似乎是,当 _.merge 返回一个新的合并对象时,_.extend 就地改变目标对象, 无论返回什么,它们似乎都会改变目标对象。 如果源对象中不存在目标对象的 _.extend clobbers 成员,这让我感到惊讶。 @JasonRice 他们不会受到打击。 For example in this fiddle, the "a" property doesn't get clobbered。确实,在extend之后,dest["p"]["y"]不再存在——这是因为在extend之前src和dest都有一个"p"属性,所以dest的"p"属性被完全覆盖了通过 src 的“p”属性(它们现在是完全相同的对象)。 明确地说,这两种方法都通过引用修改/覆盖了 first 参数。因此,如果您想从结果合并中获得一个新对象,最好传递一个对象文字。 var combined = merge(, src, dest)【参考方案5】:

另一个需要注意的区别是处理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 值合并到定义的值中。

【讨论】:

是我自己还是 lodash.extend 完全没用,因为它总是返回 'toMerge' 对象的克隆? 如果mergeInto 具有toMerge 没有的属性,那么它将保留这些属性。在那种情况下,它就不是克隆了。 @JasonRice 删除空的 并将其合并到 lodash.merge(mergeInto, toMerge)

以上是关于Lodash - .extend() / .assign() 和 .merge() 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章

慎用lodash的cloneDeep函数

javascript实现一个合并多个对象的方法

_extend用法总结

lodash的读音,lodash为什么叫lodash

lodash简介

如何在Node.js中合并两个复杂对象