前言
一直以来我都把 forEach可以改变原数组 铭记于心,直到昨天,我才发现我被这句话坑了。
平时的开发我都是用 forEach 来对对象数组进行数据处理,一般从后端接收回来的json数据都要先进行数据处理。
举个🌰
// 这里goodsList模拟请求后端返回来的数据
let goodsList = [{
id: 1,
name: 'ozoZai',
price: 99999999999,
total: 1,
}, {
id: 2,
name: '商品',
price: 10,
total: 0,
}]
goodsList.forEach((goods, index) => {
/* 检验是否无货 */
goods.soldOut = goods.total === 0 ? true : false
})
console.log(goodsList)
输出的goodsList:
[{
id: 1,
name: 'ozoZai',
price: 99999999999,
total: 1,
soldOut: false, // forEach后新加的属性
}, {
id: 2,
name: '商品',
price: 10,
total: 0,
soldOut: true, // forEach后新加的属性
}]
从上面的输出可以看出来,forEach的确改变了原来的goodsList数组。
那再看一个🌰,看看forEach改变原数组了吗?
let arr = [1, 2, 3, 4]
arr.forEach(item => {
item = 0
})
console.log(arr)
你以为打印出来的结果是 [0, 0, 0, 0] 吗?
哦豁,arr还是原来的arr,咋就没有改变原数组呢?
原理分析
我们看到MDN对Array.prototype.forEach()对解释:
The forEach() method executes a provided function once for each array element.
forEach() 方法对数组的每个元素执行一次给定的函数。
forEach does not directly mutate the object on which it is called but the object may be mutated by the calls to callbackfn.
forEach 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。
再看到forEach的语法:
arr.forEach(callback(currentValue [, index [, array]])[, thisArg])
currentValue: 数组中正在处理的当前元素。
那forEach是怎样拿到这个当前的元素的呢?
实际上forEach只是把当前元素在内存中的值复制给currentValue。
这里就会引申到JavaScript的内存空间了。
首先JavaScript的数据分为基本数据类型和引用数据类型。
基本数据类型:Number, String, Boolean, Null, Undefined, Symbol
引用数据类型:Object
内存中的栈 ⬇️
变量 | 值 |
---|---|
num | 1 |
str | ‘abc’ |
bool | true |
obj | 0x00ff12 |
对于基本数据类型,在栈内存中是按值存储,存储的就是真实的数据。
而引用数据类型,在栈内存中存储的是指向该对象的地址,需要通过引用访问的方式才能访问到对象中实际的数据。
回到forEach中,每次的currentValue得到的都是当前元素在栈内存中的值。
当前元素是基本数据类型的时候,currentValue实际上得到的值是该元素的真实的值,相当于是当前元素的副本,与当前元素没有啥关系。
当前元素是引用数据类型的时候,currentValue实际上得到的值是指向该对象的地址,当我们通过currentValue.属性的时候,访问的就是该对象本身。因为currentValue得到了该对象的地址,就相当于currentValue和该对象指向同一个地址,他们实际上是同一个对象,只是名字不同了而已。
所以当forEach操作基本数据类型的时候,并不会改变原数组中的值。而当forEach操作引用数据类型的时候,才会改变原数组中的值。
后续
那问题来了,那我们要对数组中的每一个基本数据类型元素进行修改的时候,forEach就用不着了吗?
别忘了,数组本身也是引用数据类型,所以我们要用forEach来修改基本数据类型的时候,可以通过引用访问的方式来对元素进行修改。
let arr = [1, 2, 3, 4]
arr.forEach((item, index) => {
arr[index] = 0
})
console.log(arr) // [0, 0, 0, 0]
通过这样以 数组[下标] 的引用访问形式,就可以实现在forEach中对原数组中的基本数据类型进行修改。
总结
当我们要用forEach对原数组进行操作的时候,需要以引用访问的方式进行访问数据,才能直接修改原数组。
let arr = [1, 'a', true, {a: 1}]
arr.forEach((item, index) => {
if (item instanceof Object) {
item.a = 0
} else {
arr[index] = 0
}
})
console.log(arr) // [0, 0, 0, {a:0}]