【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));

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0hWKQYA-1654572396038)(C:\Users\liqi3.SZSYY\AppData\Roaming\Typora\typora-user-images\image-20220607095345000.png)]

扩展案例分析:

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);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cIycx8xL-1654572396039)(C:\Users\liqi3.SZSYY\AppData\Roaming\Typora\typora-user-images\image-20220607100848595.png)]

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);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f7Y4rFYx-1654572396040)(C:\Users\liqi3.SZSYY\AppData\Roaming\Typora\typora-user-images\image-20220607101028953.png)]

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的

3.赋值、浅拷贝、深拷贝

(1)赋值

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

(2)浅拷贝

浅拷贝:只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做浅拷贝(浅复制)

  • 浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
  • 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
(3)深拷贝

深拷贝:在计算机中开辟了一块新的内存地址用于存放复制的对象。(对属性中所有引用类型的值,遍历到是基本类型的值为止。 )

和原数据是否指向同一对象基本数据类型的第一层数据原数据中的子对象
赋值修改会使原数据一同修改修改会使原数据一同修改
浅拷贝修改不会使原数据一同修改修改会使原数据一同修改
深拷贝修改不会使原数据一同修改修改不会使原数据一同修改

4.JS数字的类型:基本类型引用类型

js的数据类型划分方式为 基本数据类型(Undefined,Null,Boolean,Number、String)引用数据类型Object(包含 function、Array、Date)

  1. 基本的数据类型有:undefined,boolean,number,string,null。 基本类型存放在栈区,访问是按值访问的,就是说你可以操作保存在变量中的实际的值。
  2. 引用类型指的是对象。可以拥有属性和方法,并且我们可以修改其属性和方法。引用对象存放的方式是:在栈中存放对象变量标示名称和该对象在堆中的存放地址,在堆中存放数据。
  3. 对象使用的是引用赋值。当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在堆中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

更多参考博客

posted @ 2022-06-07 18:00  我有满天星辰  阅读(427)  评论(0编辑  收藏  举报