【问题标题】:Lodash - difference between .extend() / .assign() and .merge()Lodash - .extend() / .assign() 和 .merge() 之间的区别
【发布时间】:2023-03-23 14:21:01
【问题描述】:

Lodash 库中,有人可以对mergeextend / assign 提供更好的解释。

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

【问题讨论】:

    标签: javascript lodash


    【解决方案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)
        猜你喜欢
        • 2012-01-28
        • 2020-10-01
        • 2014-03-04
        • 2015-02-10
        • 2023-03-08
        • 2020-01-02
        • 2017-06-17
        • 1970-01-01
        相关资源
        最近更新 更多