首页 > 解决方案 > 在 array.prototype.map(在 RxJS 管道中)中返回副本或操作原始对象之间的区别

问题描述

我正在开发一个 Angular 9、RxJS 6 应用程序,并且对管道主题值的不同结果和在该管道中进行单位转换有疑问。

请看一下这个 stackblitz。在那里,在backend.service.ts文件内部,创建了一个可观察对象,它进行一些“单位转换”并返回所有提交给 _commodities 主题的内容。如果您查看该convertCommodityUnits函数,请注意我注释掉了工作示例,而是采用了我最初解决它的方式。

我的问题:当您使用屏幕上的取消订阅按钮并再次订阅时,当使用仅覆盖对象而不进行复制的“转换解决方案”时,HTML中的值被转换多次,因此管道不使用主体提供的原始数据。如果您使用其他代码,因此在里面创建商品对象的克隆convertCommodityUnits,它会像预期的那样工作。

现在,我不明白为什么这两种转换数据的方式如此不同。我知道一个直接操作数据,因为 js通过共享调用并且返回一个新对象。但是传递给convertCommodityUnits函数的对象是由函数创建的array.prototype.map,所以它不应该覆盖任何东西,对吧?我希望 RxJS 使用发送给主题的原始最后一个数据传递给管道/映射运算符,但在示例中似乎并非如此,对吧?

如何/为什么在这里多次转换值?

这或多或少是一个后续问题:Angular/RxJS 手动更新管道主题(即使没有数据更改),“rxjs 管道中的单位转换”,所以它是相同的设置。

标签: javascriptangularrxjsrxjs-pipeable-operators

解决方案


当您使用时,您会map获得数组的新参考。但是您不会在新生成的数组(数组的浅拷贝)中获得新对象,因此您正在改变元素内的数据。

在解构解决方案中,因为数组中的每个对象只有原始类型,所以每次调用转换方法时都会为数组生成全新的元素(这很重要:不仅是新数组,还有新元素在数组中 => 您已经执行了数组的深层复制)。因此,您不会连续累积每个订阅中的值。

这并不意味着您在提供的 stackblitz 演示中使用的 1 级解构解决方案在所有情况下都适用。我已经看到这个错误很多,特别是在需要你不改变存储数据的redux模式框架中,比如ngrx、ngxs等。如果你的数组中有复杂的对象,那么1级解构会'数组的每个元素中的所有嵌入对象都保持不变。我认为用例子来描述这种行为更容易:

const obj1 = {a: 1};
const array = [{b: 2, obj: obj1}];

// after every newArray assignment in the below code, 
// console.log(newArray === array) prints false to the console

let newArray = [...array];
console.log(array[0] === newArray[0]); // true

newArray = array.map(item => item);
console.log(array[0] === newArray[0]); // true

newArray = array.map(item => ({...item}));
console.log(array[0] === newArray[0]); // false
console.log(array[0].obj === newArray[0].obj); // true

newArray = array.map(item => ({
      ...item,
      obj: {...item.obj}
    }));
console.log(array[0] === newArray[0]); // false
console.log(array[0].obj === newArray[0].obj); // false

推荐阅读