假设我有三个对象。
const x = { val: 1 };
const y = { val: 2 };
const z = { val: 3 };
const mergedObj = _.merge(x,y,z);
// x = { val: 3 };
// y = { val: 2 };
// z = { val: 3 };
// mergedObj = { val: 3 };
为什么lodash的merge函数中第一个对象将对象x的值改为3,而对象y的值仍然为2?我认为所有对象都会保留相同的键值对,因为 _.merge 函数会创建一个新对象?我的假设错了吗?
最佳答案
Lodash 改变第一个参数而不是创建一个全新的对象。每个后续参数都按顺序应用于第一个参数之上,但它们本身保持不变。
我们可以通过创建 JavaScript setter 函数来演示这一点。每次“设置”属性时都会调用此方法(因此得名)。
let merge1 = { val: 1 };
let merge2 = { val: 2 };
let merge3 = { val: 3 };
let mergeTarget = {
val:0,
set val(name) {
console.log("Setting with:" + name);
}
}
_.merge(mergeTarget,merge1,merge2, merge3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
您会看到 mergeTarget
上的 setter 不是被调用一次而是被调用了三次,每个后续属性都按顺序调用:
Setting with:1
Setting with:2
Setting with:3
明确一点,Lodash 确实 返回对最终值的引用,这在使用合并内联等情况下很有用。例如(使用人为的例子):
someArray.map(_.merge(mappingFunction, properties))
但是,正如您所注意到的,此返回值只是对已更改的第一个参数的引用。
为什么?
至于更深层次的问题,为什么 lodash 这样做...这是一个很好的问题,毕竟通常避免 impure functions 是有意义的对于一个简单的效用函数。真的必须问问 lodash 开发人员以及在他们之前设置这种做法的 underscore 开发人员。
但我们可以很容易地想到一些很好的理由,为什么它是这样实现的,为什么它仍然存在。
<强>1。易于使用/风格。
Lodash/underscore 的部分吸引力在于它们提供了方便且定义明确的实用函数,这些函数易于自由使用。在某种程度上,它们感觉像是 JavaScript 本身的语法糖。在您只需要合并一个对象的情况下,这样就不用担心或弄乱代码了。
例如你可以这样做:
myComponent.updateConfig = function(update){
_.merge(myComponent.config, update)
}
这比担心返回值要简洁一点。考虑到副作用是明确和明确的,像这样使用它并不是一件坏事。
然而,这可以说是最不重要的原因。
<强>2。性能。
除非必要,否则避免创建过多对象是一个明智的默认设置。
JavaScript 的默认行为是通过引用传递对象,它不要求任何聪明的方法来对对象的一部分进行写时复制突变。如果您需要创建对象的修改副本,同时保持原始对象不变,唯一的方法是复制整个对象,然后对副本进行修改。
当处理只有几个属性的简单对象时,这不是问题,但假设您有一个具有数百个属性的复杂对象,并且您在应用程序的更新周期中逐步合并添加(例如)。不难想象优化和内存泄漏可能成为问题的情况。
<强>3。很容易避免。
有些情况下不希望改变对象,我们更喜欢纯函数行为。然而,通过简单地将空对象作为第一个参数传递来创建此行为非常容易。
const x = { val: 1 };
const y = { val: 2 };
const z = { val: 3 };
const result = _.merge({},x,y,z);
console.log(x.val) //will return 1
result.val = 5
console.log(x.val) //will still return 1
关于javascript - 为什么 _.merge 中的第一个对象会更新为当前值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56856584/