【JS 的数组赋值踩坑,一直是最后一个值。深、浅拷贝分析】
1.数组赋值案例(Array)
举例在某个业务中,给两个人同时记录一份消费单记录,要同步保存,在循环赋值的时候发现最后的结果,都是同一个值。数组赋值后,一直是之前的值。
//已知两个人一起消费,同时给他们记录消费记录
let totalRecordArr = [];
let userArr = [{userId:1,userName:"张三",},{userId:2,userName:"李四"}];//已知用户
let recordArr = [{recordId:"001",recordUserId:'',recordUserName:'',recordName:'啤酒'}]; //消费记录
for (let i = 0; i < userArr.length; i++) {
let newRecordArr = recordArr;
for (let j = 0; j < newRecordArr.length; j++) {
newRecordArr[j].recordUserId = userArr[i].userId;
newRecordArr[j].recordUserName = userArr[i].userName;
}
totalRecordArr = totalRecordArr.concat(newRecordArr);
}
console.log("recordArr",recordArr) ;
console.log("totalRecordArr",totalRecordArr);
造成原因:
js的对象赋值是引用赋值,传递的是地址,如果想拷贝出一份进行值的改变,就会引起被拷贝值的同时改变
JSON.parse(JSON.stringify(obj)) 解决方案
这个办法可以成功进行深拷贝,当源对象的属性值是一个指向对象的引用时,应用深度复制
//已知两个人一起消费,同时给他们记录消费记录
let totalRecordArr = [];
let userArr = [{userId:1,userName:"张三",},{userId:2,userName:"李四"}];//已知用户
let recordArr = [{recordId:"001",recordUserId:'',recordUserName:'',recordName:'啤酒'}]; //消费记录
for (let i = 0; i < userArr.length; i++) {
//let newRecordArr = recordArr; //赋值对象的引用
let newRecordArr = JSON.parse(JSON.stringify(recordArr));
for (let j = 0; j < newRecordArr.length; j++) {
newRecordArr[j].recordUserId = userArr[i].userId;
newRecordArr[j].recordUserName = userArr[i].userName;
}
totalRecordArr = totalRecordArr.concat(newRecordArr);
}
console.log("recordArr",recordArr) ;
console.log("totalRecordArr",totalRecordArr);
修改为:
let newRecordArr = JSON.parse(JSON.stringify(recordArr));
扩展案例分析:
js中储存对象都是存引用地址,所以浅拷贝会导致两个变量指向同一块内存地址。数组的赋值其实相当于给了索引,改变其中一个变量其他的引用其他都会改变。如下为浅拷贝
var a = [1,2,3]
var b = a //此步不是赋值,而是将a的引用赋给b,所以改变b也会改变a
b[0]=4
console.log("a",a) ;// a为[4,2,3]
console.log("b",b) ;// b为[4,2,3]
总的来说**:原始参数(比如一个具体的数字)被作为值传递给函数,如果被调用函数改变了这个参数的值,这样的改变不会影响到全局或调用函数。但当你传递一个对象(js里数组也是对象)到一个函数,如果在函数里面改变了这个参数的内容,那么这个改变在外部是可见的,也就是会影响到全局。**
深拷贝数组的方法:
(1)slice函数,newArr = arr.slice(0)
(2)concat函数,newArr = [].concat(arr,arr2,…)
(3)assign函数(对象),newObj = object.assign({},obj)
但以上三种方式都是对对象第一层的深拷贝,第二层之后还是浅拷贝,要实现多维数组的深拷贝可以用:
newArr = JSON.parse(JSON.stringify(arr))
2.对象案例(Object)
var obj = {id:1,name:'尼古拉斯赵四'};
var obj_copy = obj;
console.log("修改前");
console.log("obj",obj);
console.log("obj_copy",obj_copy);
obj_copy.name = "嘤嘤嘤";
console.log("改变之后");
console.log("obj",obj);
console.log("obj_copy",obj_copy);
Object.assign(target,…sources)解决方案
Object.assign()拷贝的是属性值。假如源对象的属性值是一个指向对象的引用,它也只拷贝那个引用值。
var obj = {id:1,name:'尼古拉斯赵四'};
var obj_copy = Object.assign({},obj);
console.log("修改前");
console.log("obj",obj);
console.log("obj_copy",obj_copy);
obj_copy.name = "嘤嘤嘤";
console.log("改变之后");
console.log("obj",obj);
console.log("obj_copy",obj_copy);
修改为:
var obj_copy = Object.assign({},obj);
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
3.赋值、浅拷贝、深拷贝
(1)赋值
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
(2)浅拷贝
浅拷贝:只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做浅拷贝(浅复制)
- 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
- 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
(3)深拷贝
深拷贝:在计算机中开辟了一块新的内存地址用于存放复制的对象。(对属性中所有引用类型的值,遍历到是基本类型的值为止。 )
和原数据是否指向同一对象 | 基本数据类型的第一层数据 | 原数据中的子对象 | |
---|---|---|---|
赋值 | 是 | 修改会使原数据一同修改 | 修改会使原数据一同修改 |
浅拷贝 | 否 | 修改不会使原数据一同修改 | 修改会使原数据一同修改 |
深拷贝 | 否 | 修改不会使原数据一同修改 | 修改不会使原数据一同修改 |
4.JS数字的类型:基本类型和引用类型
js的数据类型划分方式为 基本数据类型(Undefined,Null,Boolean,Number、String)
和 引用数据类型Object(包含 function、Array、Date)
- 基本的数据类型有:undefined,boolean,number,string,null。 基本类型存放在栈区,访问是按值访问的,就是说你可以操作保存在变量中的实际的值。
- 引用类型指的是对象。可以拥有属性和方法,并且我们可以修改其属性和方法。引用对象存放的方式是:在栈中存放对象变量标示名称和该对象在堆中的存放地址,在堆中存放数据。
- 对象使用的是引用赋值。当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在堆中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
更多参考博客