为什么一个数组上的js映射会修改原来的数组?
Why does a js map on an array modify the original array?
我对 map() 的行为很困惑。
我有一个这样的对象数组:
const products = [{
...,
'productType' = 'premium',
...
}, ...]
我将这个数组传递给一个函数,该函数应该 return 相同的数组但所有产品都是免费的:
[{
...,
'productType' = 'free',
...
}, ...]
函数为:
const freeProduct = function(products){
return products.map(x => x.productType = "free")
}
return以下数组:
["free", "free", ...]
所以我将函数重写为:
const freeProduct = function(products){
return products.map(x => {x.productType = "free"; return x})
}
其中 return 是预期的数组。
但是!这就是我失去理智的时刻,在这两种情况下我的原始产品数组都被修改了。
关于 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})
}
但我仍然得到相同的结果(这开始让我发疯)。
如果有人能解释我做错了什么,我将不胜感激!
谢谢。
您没有修改原始数组。您正在修改数组中的对象。如果您想避免改变数组中的对象,您可以使用 Object.assign
创建一个新对象,其中包含原始属性以及您需要的任何更改:
const freeProduct = function(products) {
return products.map(x => {
return Object.assign({}, x, {
productType: "free"
});
});
};
2018 编辑:
在 most browsers 中,您现在可以使用对象传播语法代替 Object.assign
来完成此操作:
const freeProduct = function(products) {
return products.map(x => {
return {
...x,
productType: "free"
};
});
};
详细说明 SimpleJ 的答案 - 如果您 === 这两个数组,您会发现它们不相等(内存中的地址不同),确认映射数组实际上是一个新数组。问题是你要返回一个新数组,它充满了对原始数组中相同对象的引用(它不是返回新的对象文字,而是返回对同一对象的引用)。因此,您需要创建新对象,这些新对象是旧对象的副本 - 即,带有 SimpleJ 给出的 Object.assign 示例。
不幸的是,无论是展开运算符还是对象赋值运算符都进行深拷贝....您需要使用类似 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!
Array Iterator Array.map() 创建具有相同数量元素的新数组或不更改原始数组。如果数组中有对象,则引用可能会出现问题,因为它复制了相同的引用,因此,当您对对象的 属性 进行任何更改时,它会更改包含的元素的原始值相同的引用。
解决方案是复制对象,好吧,array.Splice()
和 [...array]
(传播运算符)在这种情况下无济于事,您可以使用 JavaScript 实用程序库,例如 Loadash 或者只使用下面提到的代码:
const newList = JSON.parse(JSON.stringify(orinalArr))
数组解构赋值可用于克隆对象。
const freeProduct = function(products){
p = products.splice()
return p.map(({...x}) => {x.productType = "free"; return x})
}
该方法不会修改原始对象。
我对 map() 的行为很困惑。
我有一个这样的对象数组:
const products = [{
...,
'productType' = 'premium',
...
}, ...]
我将这个数组传递给一个函数,该函数应该 return 相同的数组但所有产品都是免费的:
[{
...,
'productType' = 'free',
...
}, ...]
函数为:
const freeProduct = function(products){
return products.map(x => x.productType = "free")
}
return以下数组:
["free", "free", ...]
所以我将函数重写为:
const freeProduct = function(products){
return products.map(x => {x.productType = "free"; return x})
}
其中 return 是预期的数组。
但是!这就是我失去理智的时刻,在这两种情况下我的原始产品数组都被修改了。
关于 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})
}
但我仍然得到相同的结果(这开始让我发疯)。
如果有人能解释我做错了什么,我将不胜感激!
谢谢。
您没有修改原始数组。您正在修改数组中的对象。如果您想避免改变数组中的对象,您可以使用 Object.assign
创建一个新对象,其中包含原始属性以及您需要的任何更改:
const freeProduct = function(products) {
return products.map(x => {
return Object.assign({}, x, {
productType: "free"
});
});
};
2018 编辑:
在 most browsers 中,您现在可以使用对象传播语法代替 Object.assign
来完成此操作:
const freeProduct = function(products) {
return products.map(x => {
return {
...x,
productType: "free"
};
});
};
详细说明 SimpleJ 的答案 - 如果您 === 这两个数组,您会发现它们不相等(内存中的地址不同),确认映射数组实际上是一个新数组。问题是你要返回一个新数组,它充满了对原始数组中相同对象的引用(它不是返回新的对象文字,而是返回对同一对象的引用)。因此,您需要创建新对象,这些新对象是旧对象的副本 - 即,带有 SimpleJ 给出的 Object.assign 示例。
不幸的是,无论是展开运算符还是对象赋值运算符都进行深拷贝....您需要使用类似 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!
Array Iterator Array.map() 创建具有相同数量元素的新数组或不更改原始数组。如果数组中有对象,则引用可能会出现问题,因为它复制了相同的引用,因此,当您对对象的 属性 进行任何更改时,它会更改包含的元素的原始值相同的引用。
解决方案是复制对象,好吧,array.Splice()
和 [...array]
(传播运算符)在这种情况下无济于事,您可以使用 JavaScript 实用程序库,例如 Loadash 或者只使用下面提到的代码:
const newList = JSON.parse(JSON.stringify(orinalArr))
数组解构赋值可用于克隆对象。
const freeProduct = function(products){
p = products.splice()
return p.map(({...x}) => {x.productType = "free"; return x})
}
该方法不会修改原始对象。