深拷贝和浅拷贝

106 阅读5分钟

1、什么是深拷贝和浅拷贝?

在面试的时候经常会碰到面试官问什么是深拷贝和浅拷贝。如何区别深拷贝和浅拷贝,简单来说,假设B复制了A,当A修改时,看B的值是否会发生变化,如果B也跟着变化了,说明这是浅拷贝,如果B没变化,那就是深拷贝,我们先看两个简单的案列: 案列一:

	var a1 = 1,a2 = a1;
		console.log(a1)  //1
		console.log(a2)  //1
		a2 = 2;  //修改a2
		console.log(a1)  //1
		console.log(a2)  //2

案例二:

	var o1 = { x: 1, y: 1}, o2 = o1;
                console.log(o1)   //{x: 1, y: 1}
                console.log(o2)   //{x: 1, y: 1}
                o2.x = 2  //修改o2
                console.log(o1)   //{x: 2, y: 1}
                console.log(o2)   //{x: 2, y: 1}

按照常规思维,o1应该和a1一样,不会因为另外一个值的改变而改变,而这里的o1 却随着o2的改变而 改变了。同样是变量,为什么表现不一样呢?为了更好的理解js的深浅拷贝,我们先来理解一些js基本的 概念 —— 目前JavaScript有五种基本数据类型(也就是简单数据类型),它们分别是:Undefined, Null,Boolean,Number和String。还含有一种复杂的数据类型(也叫引用类型),就是对象; 引用类型常见的有:Object、Array、Function等

2、基本类型和引用类型

ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值基本类型值指的是那些保 存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值是指那些保存堆内存中的对象意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置保存对象。那什么又是堆?什么又是栈?``

3、js中的推内存和占内存

栈:javascript的基本类型就5种:Undefined、Null、Boolean、Number和String,它们都是直接按值存储在栈中,每种类型的数据占用的内存空间的大小是确定的 栈由系统自动分配, 例如,声明在函数中一个局部变量var a; 系统自动在栈中为a开辟空间 只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出

堆:javascript中其他类型的数据被称为引用类型的数据 : 如对象(Object)、数组(Array)、函数(Function) …, 它们是通过拷贝和new出来的,这样的数据存储于堆中 其实,说存储于堆中,也不太准确,因为,引用类型的数据的地址指针是存储于栈中的,当我们想要访 问引用类型的值的时候,需要先从栈中获得对象的地址指针,然后,在通过地址指针找到堆中的所需要的数据

image.png

引用关系的拷贝过程

var obj1 = {id:1,name:'obj1'};
obj2 = obj1; 
//相当于只是复制了一个指针地址,obj1和obj2实际指向的是同一数据对象 所以 obj2.id = 2; obj1.id 的结果会同步变化

image.png

4、引用类型如何实现深拷贝

既然已经知道了深拷贝与浅拷贝的来由,那么该如何实现深拷贝?我们先分别看看Array和Object自有方法是否支持:

1、Array

对于数组我们可以用slice() 和 concat() 方法来解决上面的问题

1)slice

var arr1 = ['a','b','c'],arr2=arr1.slice();
        console.log(arr1)       //['a','b','c']
        console.log(arr2)	//['a','b','c']
        arr2[0] = 'e'       //修改arr2的第0的值 为 'e'
        console.log(arr1)	//['a','b','c']
        console.log(arr2)	//['e','b','c']
        此时,arr2的值修改并没有影响到arr1

2)concat

var arr1 =['a','b'],arr2 = arr1.concat();
	console.log(arr1)   //['a','b']
	console.log(arr2)   //['a','b']
	arr2[0] = 'e'
	console.log(arr1)   //['a','b']
	console.log(arr2)   //['e','b']

3)多维数组也可以吗? 答案:不可以

var arr1 = ['a','b',['c','d']], arr2 = arr1.concat();
	arr2[2][1] = 100;  //修改arr2
	console.log(arr1) // ['a','b',['c',100]]
	console.log(arr2) // ['a','b',['c',100]]
	// 此时可以看到 修改arr2 改变了arr1 看来slice() 和 concat() 只能实现一维数组的深拷贝

2、 有什么方式可以实现深拷贝呢

1)JSON.parse(JSON.stringify(obj))

var obj = {
	name: 'llyao',
	age: 18
	}
	var obj2 = JSON.parse(JSON.stringify(obj));
	obj.name = 'alice';
	console.log(obj) // {name: "alice", age: 18}
	console.log(obj2) // {name: "llyao", age: 18}

JSON.parse(JSON.stringify(obj))深拷贝的坑

  1. 如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
  2. 如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象。
  3. 如果obj里有函数,undefined,则序列化的结果会把函数, undefined丢失。
  4. 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
  5. JSON.stringify()只能序列化对象的可枚举的自有属性。如果obj中的对象是有构造函数生成的, 则使 用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
  6. 如果对象中存在循环引用的情况也无法正确实现深拷贝。

5、利用递归封装一个方法 deep来实现对象的深拷贝

var Animal={
		name: "cat",
		skin: ["red", "green"],
		child: {
			xxx: "xxx"
		},
		say: function(){
			console.log("I am ", this.name, " skin:", this.skin)
		}
	}
	
	function deep(dest, obj){   //dest 默认是一个空对象  obj 需要拷贝的数组
		var o = dest;  
		for (var key in obj) {   //使用for in 遍历对象的每一项
			if (typeof obj[key] === 'object'){ //判断每一项的值  如果是 则再次调用deep()函数 ..递归调用
				o[key] = (obj[key].constructor===Array)?[]:{};  //constructor 判断类型是数组还是对象
				deep(o[key], obj[key])
			} 
			else {      //如果不是 直接赋值
				o[key] = obj[key]
			}
		}
		return o;
	}
	// 调用
	var Animals = deep({},Animal)
	console.log(Animals)  
	/**
	 * {
			name: "cat",
			skin: ["red", "green"],
			child: {
				xxx: "xxx"
			},
			say: function(){
				console.log("I am ", this.name, " skin:", this.skin)
			}
		}
	 */
	
        
	Animals.name = "dog" //修改Animals 的数据
        Animals.child.xxx="aaa" //修改Animals 的数据
	console.log(Animals)
	/**
	 * {
			name: "dog",
			skin: ["red", "green"],
			child: {
				xxx: "aaa"
			},
			say: function(){
				console.log("I am ", this.name, " skin:", this.skin)
			}
		}
	 */
	console.log(Animal)
	/**
	 * {
			name: "cat",
			skin: ["red", "green"],
			child: {
				xxx: "xxx"
			},
			say: function(){
				console.log("I am ", this.name, " skin:", this.skin)
			}
		}
	 */