浅析JS中的赋值/浅拷贝/深拷贝

在了解深浅拷贝之前,需要先明白一个概念:堆和栈

堆和栈

堆和栈都是内存中划分出来用来存储的区域。栈(stack)为自动分配的内存空间,它由系统自动释放;而堆(heap)则是动态分配的内存,大小不定也不会自动释放。

JS中的数据类型

基本数据类型存放在栈中

基本数据类型:undefined,boolean,number,string,null

var num = 123;
var str="abc";

以上数据在栈中的存储形式如下图:
在这里插入图片描述

引用类型的数据存放在堆中

JS中除了基本数据类型以外的都是对象,如对象、数组、函数等,当声明这些数据时,其变量名和堆地址存放在栈中,而数据存放在堆中。

var obj = {name: 'xiaoming'};
var arr = [1,2,3];

以上数据在堆和栈中的存储形式如下图:
在这里插入图片描述

传值与传址

了解了基本数据类型与引用类型的区别之后,我们就应该能明白传值与传址的区别了。
在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。例如:

var a = 123;
var b = a;

a++;
console.log(a); //124
console.log(b); //123

以上代码在栈中的过程如下图:
在这里插入图片描述
由此可见,对于基本数据类型的赋值,两个变量是两个独立相互不影响的变量,改变其中一个并不会影响另一个的值。
而引用数据类型的赋值是传址。也就是赋值后取到的是其堆内存地址,所以最终两个变量会指向同一个堆内存地址,因此两者之间操作互相有影响。

var obj1 = {name: 'zhangsan'};
var obj2 = obj1;

obj2.name = 'lisi';
console.log(obj1.name); // lisi
console.log(obj2.name); // lisi

当我们改变obj2.name的值时,obj1.name的值也同时被修改。
此时我们假设obj1指向的堆内存地址为x0011,下面我们已图的形式看看这个过程。
在这里插入图片描述
由此可见,当我们将obj1赋值给obj2时,实际上只是把obj1中存储的堆内存地址(x0011)赋给了obj2,实际上obj1和obj2指向的是同一片堆空间,因此当改变obj2.name时,obj1也会一同改变。

赋值VS浅拷贝

那么赋值和浅拷贝有什么区别呢,我们来一个例子:

var obj1 = {
			  name: 'zhangsan',
       	 	  age:  18,
        	  arr: [1, 2, 3]
        	}
var obj2 = obj1  //赋值

var obj3 = shallowCopy(obj1) //浅拷贝
function shallowCopy(dest={}, obj){
	for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            dest[prop] = obj[prop];
        }
    }
    return dest;
}

//改变obj2
obj2.name = 'lisi';
obj2.age = 20;
obj2.arr = [3, 4, 5];
console.log(obj1);
//obj1 = {
//	       name: 'lisi',
//    	   age:  20,
//         arr: [3, 4, 5]
//       }

//改变obj3
obj3.name = 'wangwu';
obj3.age = 22;
obj3.arr = [6, 7, 8];
console.log(obj1);
//注意,此时obj1已经跟随obj2改变过了
//obj1 = {
//	       name: 'lisi',
//    	   age:  20,
//         arr: [6, 7, 8]
//       }
console.log(obj3);
//obj1 = {
//	       name: 'wangwu',
//    	   age:  22,
//         arr: [6, 7, 8]
//       }

通过上面的例子,我们不难看出来,通过赋值得来的obj2,在改变obj2中的任何值时(包括基本数据类型和引用类型),obj1都会跟着改变。

而通过浅拷贝得到的obj3,在修改obj3中的基本数据类型的值时,obj1不会受到影响,而修改obj3中的引用类型数据时,obj1同样会跟着改变。我们可以把obj1中的基本数据类型看作第一层,引用数据类型看成第二层。也就是说通过浅拷贝获得的对象,在改变其第一层数据时并不会影响到原对象中的数据,而改变其第二层数据时,原对象仍然会受到影响。

而第二层会跟着改变的原因,仍然是其中的引用数据类型在栈中存储的是堆内存地址造成的。

那要怎么样才能使在改变第二层甚至第n层的时候,原对象不会受到影响呢?这就是深拷贝的作用。

深拷贝

深拷贝是对对象以及对象的所有子对象进行拷贝。

那么问题来了,怎么进行深拷贝呢?
思路就是递归调用刚刚的浅拷贝,把所有属于对象的属性类型都遍历赋给另一个对象即可。由于本文旨在讲清楚赋值、浅拷贝、深拷贝的概念及区别,所以下面的深拷贝函数是简化版,只考虑子对象为数组或者对象类型。

var Person = {
			    name: 'zhangsan',
			    age: 18,
			    other: {
			  		   str: 'abc',
			  		   arr: [1, 2, 3]
			  	     }
			  }
			  
function deepCopy(dest={}, obj){
	//遍历原对象
	for(var key in obj){
		//判断数据是否为引用类型
		if(typeof obj[key] === 'object'){
			//判断数据为数组类型还是对象类型,并进行初始化
			dest[key] = Object.prototype.toString.call(obj) == {} ? {} : [];
			//递归
			deepCopy(dest[key], obj[key])
		}else {
			//非引用类型则直接赋值
			dest[key] = obj[key];
		}
	}
	return dest;
}

var obj2 = deepCopy(dest={}, Person)
obj2.other.str = 'xyz'
obj2.other.arr = [4 ,5, 6]

console.log(obj1.other.str) //'abc'
console.log(obj1.other.arr) //[1, 2, 3]
console.log(obj2.other.str) //'xyz'
console.log(obj2.other.arr) //[4 ,5, 6]
总结:
和原数据是否指向同一对象第一层数据为基本数据类型原数据中包含子对象
赋值改变会使原数据一同改变改变会使原数据一同改变
浅拷贝改变会使原数据一同改变改变会使原数据一同改变
深拷贝改变会使原数据一同改变改变会使原数据一同改变
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值