为什么_.merge中的第一个对象被更新为当前值?

时间:2019-07-02 16:21:27

标签: javascript object lodash

说我有三个物体。

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的合并功能中的第一个对象将对象x的值更改为3,而对象y仍保持为2?我认为所有对象都将保留相同的键值对,因为_.merge函数会创建一个新对象?我在这个假设上错了吗?

3 个答案:

答案 0 :(得分:2)

根据documentation,此方法会更改原始对象。因此x将被突变。另请注意,mergedObjx是相同的。这与Object.assign

的工作方式相同

const x = { val: 1 };
const y = { val: 2 };
const z = { val: 3 };

const mergedObj = _.merge(x,y,z);
console.log(mergedObj === x)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

  

为什么对象y保留在2

在此lodash方法中,传递的第一个元素参数被视为目标对象。下一个传递的对象(x)的所有属性都将传递到该对象(y and z)。因此下一个对象将不会发生突变。

如果要创建一个新对象。然后使用空对象{}作为目标对象(第一个参数)

const x = { val: 1 };
const y = { val: 2 };
const z = { val: 3 };
let merged = _.merge({},x,y,z);
console.log(merged);
console.log(x);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

答案 1 :(得分:2)

在lodash合并中,第一个参数是目标对象。是将被突变的对象。也是将要返回的对象(有关含义,请参见下文)。

lodash合并那些不创建新对象的对象。没有创建新对象。而是,它使第一个对象发生突变(更改其值)。

其余参数是要合并的对象。他们不会变异。合并将按顺序应用。

ref:https://lodash.com/docs/4.17.11#merge

因此,这里的x对象将被突变为x.val=2,然后是x.val=3x.val的最终值为3。y和z不变。合并函数的结果将为x。

这是怎么想的

const x = { val: 1 };
const y = { val: 2 };
const z = { val: 3 };

const refToX = _.merge(x,y,z);

// Since the result is a ref to x varaiable, this means
refToX.val=5;
console.log(x.val) //will return 5

答案 2 :(得分:2)

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开发人员以及下划线开发人员提出要求,然后才将这种做法落实到位。

但是我们可以很容易地想到一些很好的理由,为什么要像这样实现它,为什么仍然如此。

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