深入理解关于forEach、map等循环方法无法修改当前遍历值
前言
在对一些数组进行遍历、处理时,JS自带的遍历方法有很多种,往往不加留意,就可能导致知识混乱的现象,并且其中还存在一些坑。
实验
示例一
let arrList = [
1,
'1',
{ a: 1, b: '1' },
[1, '1'],
undefined,
null
]
arrList.forEach(item => {
item = 2
})
// arrList.map(item => {
// item = 2
// })
console.log(arrList) //[1,'1',{ a: 1, b: '1' },[1, '1'],undefined,null]
可以发现,当前数组并没有发生改变
示例二
let arrList = [
1,
'1',
{ a: 1, b: '1' },
[1, '1'],
undefined,
null
]
arrList.forEach(item => {
if(Object.prototype.toString.call(item) === '[object Object]'){
item.a = 2
item.b = '2'
}
if(Object.prototype.toString.call(item) === '[object Array]'){
item[0] = 2
item[1] = '2'
}
})
console.log(arrList) //[1,'1',{ a: 2, b: '2' },[2, '2'],undefined,null]
在这里我们把对象 {a:1,b:‘1’} 改变了 { a: 2, b: ‘2’ },数组[1,‘1’]也变成了[2,‘2’]
那这是为什么呢?
数据类型
这我们就要先搞懂数据类型了
最新的 ECMAScript 标准定义了 8 种数据类型:
7 种原始类型,使用 typeof 运算符检查:
- undefined:typeof instance === “undefined”
- Boolean:typeof instance === “boolean”
- Number:typeof instance === “number”
- String:typeof instance === "string
- BigInt:typeof instance === “bigint”
- Symbol :typeof instance === “symbol”
- null:typeof instance === “object”
Object:typeof instance === “object”。任何 constructed 对象实例的特殊非数据结构类型,也用做数据结构:new Object,new Array,new Map,new Set,new WeakMap,new WeakSet,new Date,和几乎所有通过 new keyword 创建的东西。
又分为两大类型
- 基本类型: null,undefined,boolean,number,string,symbol
- 引用类型:Object: Array ,Function, Date, RegExp等
基本类型和引用类型
- 基本数据类型:基本类型值在内存中占据固定大小,直接存储在栈内存中的数据
- 引用数据类型:引用类型在栈中存储了指针,这个指针指向堆内存中的地址,真实的数据存放在堆内存里。
也就是说,基本类型的值是直接访问的,而引用类型的值是存在堆内存中,通过栈内存中的地址指向堆内存中的真实数据进行访问的。
结论
这里大概也能猜到在执行forEach
遍历方法的时候做了些什么事情了。
也就是说,当前遍历的数组不是原数组
所以当直接item = 2
这样直接赋值修改的时候是没有修改原数组的,只是修改了当前遍历的数组。
而为什么 item.b = '2'
就可以修改里面的值呢?
通过先前理解的基本类型和引用类型可以理解
当遍历到当前数组里面的对象的时候 当前item
拿到是当前对象的堆内存的地址,而不是真实的栈内存的真实数据,所以当前操作的是通过该地址指向
当前引用类型
的真实数据
而当前遍历数组和原数组是共同同一个地址指向
,所以就能理解为什么修改当前遍历数组的对象,原数组也会改变了。