为什么数组上的js映射会修改原始数组?

问题描述 投票:0回答:6

我对map()的行为感到很困惑。

我有一个像这样的对象数组:

const products = [{
    ...,
    'productType' = 'premium',
    ...
}, ...]

我将此数组传递给一个函数,该函数应该返回相同的数组,但所有产品都是免费的:

[{
    ...,
    'productType' = 'free',
    ...
}, ...]

功能是:

const freeProduct = function(products){
    return products.map(x => x.productType = "free")
}

返回以下数组:

["free", "free", ...]

所以我将函数重写为:

const freeProduct = function(products){
    return products.map(x => {x.productType = "free"; return x})
}

这会按预期返回数组。

但是!那是我失去理智的那一刻,在这两种情况下,我的原始产品数组都被修改了。

关于map()的文档说它不应该(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)。

我什至尝试创建数组的克隆,将我的函数变成这样:

const freeProduct = function(products){
    p = products.splice()
    return p.map(x => {x.productType = "free"; return x})
}

但我仍然得到相同的结果(这开始让我发疯)。

我将非常感谢任何可以解释我做错了什么的人!

谢谢你。

javascript node.js dictionary functor
6个回答
118
投票

您没有修改原始数组。您正在修改数组中的对象。如果您想避免改变数组中的对象,您可以使用

Object.assign
创建一个新对象,其中包含原始对象的属性以及您需要的任何更改:

const freeProduct = function(products) {
  return products.map(x => {
    return Object.assign({}, x, {
      productType: "free"
    });
  });
};

2018年编辑:

大多数浏览器中,您现在可以使用对象扩展语法而不是

Object.assign
来完成此操作:

const freeProduct = function(products) {
  return products.map(x => {
    return {
      ...x,
      productType: "free"
    };
  });
};

17
投票

详细说明 SimpleJ 的答案 - 如果您 === 两个数组,您会发现它们不相等(内存中的地址不同),从而确认映射的数组实际上是一个新数组。问题是您正在返回一个新数组,其中充满了对原始数组中相同对象的引用(它不是返回新的对象文字,而是返回对同一对象的引用)。因此,您需要创建作为旧对象副本的新对象 - 即,使用 SimpleJ 给出的 Object.assign 示例。


5
投票

不幸的是,无论是扩展运算符还是对象分配运算符都进行深层复制......您需要使用类似 lodash 的函数来获取真实副本而不仅仅是引用副本。

const util = require('util');
const print = (...val) => {
    console.log(util.inspect(val, false, null, false /* enable colors */));
};
const _ = require('lodash');

const obj1 =     {foo:{bar:[{foo:3}]}};
const obj2 =     {foo:{bar:[{foo:3}]}};

const array = [obj1, obj2];

const objAssignCopy = x => { return Object.assign({}, x, {})};
const spreadCopy = x => { return {...x}};
const _Copy = x => _.cloneDeep(x);

const map1 = array.map(objAssignCopy);
const map2 = array.map(spreadCopy);
const map3 = array.map(_Copy);

print('map1', map1);
print('map2', map2);
print('map3', map3);
obj2.foo.bar[0].foo = "foobar";
print('map1 after manipulation of obj2', map1); // value changed 
print('map2 after manipulation of obj2', map2); // value changed
print('map3 after manipulation of obj2', map3); // value hasn't changed!

2
投票

数组迭代器 Array.map() 创建具有相同元素数量的新数组,或者不更改原始数组。如果数组中存在对象,因为它复制相同的引用,则引用可能会出现问题,因此,当您对对象的属性进行任何更改时,它将更改保存相同引用的元素的原始值。

解决方案是复制对象,好吧,

array.Splice()
[...array]
(扩展运算符)在这种情况下没有帮助,您可以使用像Loadash这样的JavaScript实用程序库或仅使用下面提到的代码:

const newList = JSON.parse(JSON.stringify(orinalArr))

1
投票

数组解构赋值可用于克隆对象。

const freeProduct = function(products){
    p = products.splice()
    return p.map(({...x}) => {x.productType = "free"; return x})
}

此方法不会修改原始对象。


0
投票

对于寻找一种现代且更好的方法来深度复制对象的人,您可以使用

structuredClone(obj)
,它会返回深度克隆的新对象。

此方法自 ECMAScript 2021 (ES12) 起可用,这里是文档

在此解决方案中,代码为:

const freeProduct = function(products) {
  return products.map(x => {
    const clone = structuredClone(x);
    clone.productType = "free";
    return clone;
  });
};

对于简单对象数组(一级深度),我认为 SimpleJ 答案 使用扩展运算符更简单。

© www.soinside.com 2019 - 2024. All rights reserved.