js中map是否会影响元素组
先说结论:
当数组中的是普通类型,不会影响元素组,如果数组中的项是引用型类型,不一定会改变。
项目中遇到的问题
- 先说说最近在项目中(ps: 我们的项目是座屎山,经历了多人的手)遇到的问题:
在现在模块化的项目中(我使用的redux + react),我在父组件中从redux状态管理器中拿到了images一个对象数组
images = [{
id: xx,
url:xx,
//等等
}]
然后从父组件中通过props传给了孙子组件,然后再孙子组件中通过修改其中某一项中的值,然后通过回到函数传回父组件中,再存入redux管理器中,父组件的回调函数是这样的:
rejectCheckBoxCB = (data) => {//data是孙子组件中修改后的值
let me = this;
let images = me.props.auditContent.images;
images.map(i => {
if (i.id === data.id) {
i = data;
}
return i;
});
me.props.actions.updateAuditContent({images: images});//重新传入redux
};
然后在之前的情况下没有任何问题,每次在孙子组件中修改后也能同步到redux
直到:
我在父组件的子组件中,也就是孙子组件和父组件的中间的一层做出了修改,我将父组件传入的images, 存入了本页面的state中(因为别的原因我要在本页面中操作images),然后在state中取得images,再作为props传给了孙子组件。。。。。,本来对于逻辑来说是没有任何问题,但是,就是因为我存了一下state,导致了线上的bug的产生。
解释原因:
接下来说一下原因:就是map中的一项被赋值,影不影响原来数组的问题:
let arr = [1, 2,3]
let arr_new =arr.map(item => {
item = item *2
return item
})
console.log(arr) //[1, 2, 3] 看出作为简单类型元素组是不会做出改变的
console.log(arr_new ) //[2, 4, 6]
那么什么情况下可以影响元素组呢?如果每一项的类型为引用型类型,并且直接修改他的属性值:
let arr = [{a:1}, {a:2}]
let arr_new = arr.map(item => {
item.a = 5
return item
})
console.log(arr) //[{a:5}, {a:5}] 看出影响了元素组
console.log(arr_new ) //[{a:5}, {a:5}]
but 但是:
是不是只要是引用型类型都会改变呢?例如:
let arr = [{a:1}, {a:2}]
let arr_new = arr.map(item => {
item = {a:5, b:5}
return item
})
console.log(arr) //[{a:1}, {a:2}] 并没有影响元素组
console.log(arr_new ) //[{a:5}, {a:5}]
这又是为啥???
因为在存入引用类型的时候存储变量的值是一个内存地址,如果你使用item.a去修改,顺着指针修改找到堆里面的存储的值,将数据改变之后,你读取arr直接读取修改后被在修改后地址的值
第一种:
初始状态:
栈(Stack)
arr -> Heap地址1
堆(Heap)
Heap地址1: [
Heap地址2: { a: 1 },
Heap地址3: { a: 2 }
]
图示:
Stack: arr ---> Heap地址1 ----> [ Heap地址2 { a: 1 }, Heap地址3 { a: 2 } ]
-
执行
map
方法map
方法遍历数组中的每个对象,item
是对堆中对象的引用。- 修改
item.a = 5
时,直接操作堆中对象(Heap地址2
和Heap地址3
),因此arr
的内容会被修改。
修改过程:
- 遍历第一个元素(
item
引用的是Heap地址2
),将其属性a
改为 5。 - 遍历第二个元素(
item
引用的是Heap地址3
),将其属性a
改为 5。
修改后的内存如下:
栈(Stack)
arr -> Heap地址1 arr_new2 -> Heap地址1
堆(Heap)
Heap地址1: [ Heap地址2: { a: 5 }, Heap地址3: { a: 5 } ]
图示:
Stack: arr ---> Heap地址1 ----> [ Heap地址2 { a: 5 }, Heap地址3 { a: 5 } ] arr_new2 --也一样
-
返回新数组
arr_new2
arr_new2
是一个新数组,但它的每个元素是对堆中原有对象的引用,因此与arr
共享同一组对象。
第二种:
初始状态:
栈(Stack)
arr -> Heap地址1
堆(Heap)
Heap地址1: [
Heap地址2: { a: 1 },
Heap地址3: { a: 2 }
]
图示:
Stack: arr ---> Heap地址1 ----> [ Heap地址2 { a: 1 }, Heap地址3 { a: 2 } ]
-
执行
map
方法map
遍历arr
中的每个元素。- 每次迭代,
item
是对数组中某个对象的引用(例如Heap地址2
或Heap地址3
)。
第一步:
item = { a: 5, b: 5 }
item
是一个局部变量,它一开始引用堆中的原对象(例如Heap地址2
)。- 通过
item = { a: 5, b: 5 }
,重新赋值给item
,使其指向一个新的堆对象(例如Heap地址4
)。 - 重新赋值只改变了
item
变量的引用,不会影响原数组中的对象。
第二步:返回新对象
- 新对象
{ a: 5, b: 5 }
被返回并存储到新数组arr_new
中。 - 原数组
arr
不会被修改,因为原始引用仍然保留在arr
中。
修改后的内存如下:
栈(Stack)
arr -> Heap地址1 arr_new -> Heap地址5
堆(Heap)
Heap地址1: [ Heap地址2: { a: 1 }, Heap地址3: { a: 2 } ] Heap地址5: [ Heap地址4: { a: 5, b: 5 }, Heap地址6: { a: 5, b: 5 } ]
图示:
Stack: arr ---> Heap地址1 ----> [ Heap地址2 { a: 1 }, Heap地址3 { a: 2 } ] arr_new ---> Heap地址5 ----> [ Heap地址4 { a: 5, b: 5 }, Heap地址6 { a: 5, b: 5 } ]
为什么 arr
不会改变?
- 引用 vs 重新赋值
- 在
map
回调中,item
是对arr
中对象的引用。 - 通过
item = { a: 5, b: 5 }
,你只是让item
指向一个新的对象,而没有修改原对象。 - 因此,原数组
arr
中的对象保持不变。
- 在
- 局部作用域
item
是回调函数的局部变量,重新赋值不会影响外部数组。arr
的元素仍然引用原来的对象。