在说数组和对象的浅拷贝,深拷贝的问题前我们先了解一下javaScript的变量类型。
(1)基本类型:
5种基本数据类型Undefined、Null、Boolean、Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。
(2)引用类型:
存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。
JavaScript存储对象都是存地址的,所以浅拷贝会导致 obj1 和obj2 指向同一块内存地址。 改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变, 而深拷贝是开辟一块新的内存地址,将原对象的各个属性逐个复制进去。 对拷贝对象和源对象各自的操作互不影响。
先看下下边例子:
var a = 10; var b = a; b = 20; console.log(a);//10 console.log(b);//20
上边代码在修改b的值时并不会改到a。
但是对象和数组就不一样了,对象和数组是按引用传值,具体如下:
//浅拷贝,双向改变,指向同一片内存空间 var obj = {'age':20} var newobj = obj newobj.age= 30 console.log(obj.age); //30 console.log(newobj.age); // 30
上边的代码在修改新对象newobj的age值,原来的对象obj的age值也改变了,为什么呢?因为对象和数组是按引用传值,所以他们根本是同一个对象,上面代码中,newobj 并不是obj 的克隆,而是指向同一份数据的另一个指针。修改newobj ,会直接导致obj 的变化。这就是所谓的浅拷贝。
var obj1 = { a: 10, b: 20, c: 30 }; var obj2 = { a: obj1.a, b: obj1.b, c: obj1.c }; obj2.b = 100; console.log(obj1); // { a: 10, b: 20, c: 30 } 这里b 沒被改到 console.log(obj2); // { a: 10, b: 100, c: 30 }
上面的代码修改拷贝的对象并不会影响原来的对象,这就是属于深拷贝的类型。
关于浅拷贝:浅拷贝有很多种方式,但是最常用的一种就是“=”赋值类型,如下
var obj1 = {'age':30} var obj2 = obj1
1、数组的拷贝
(注意下边这几种数组拷贝其实是数组的浅拷贝,但是我们可以把他当做数组的深拷贝来用,但事实是浅拷贝)
方法1:循环遍历
var arr1 = [1,2,3] var arr2 = [] for(var i=0;i<arr1.length;i++){ arr2[i] = arr1[i] } arr2[1]=100 console.log(arr1 );//[1,2,3] console.log(arr2 );//[1,100,3]
方法2:concat方法,该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
var arr1 = [1,2,3] var arr2 = [].concat(arr1) arr2[1]=100 console.log(arr1 );//[1,2,3] console.log(arr2 );//[1,100,3]
方法3:es6扩展运算符
var arr1 = [1,2,3] var arr2 = [...arr1] arr2[1]=100 console.log(arr1);//[1,2,3] console.log(arr2);//[1,100,3]
到这里或许你已经发现问题了,我们给的例子中的数组都是一维数组,如果换成二维数组,上边的方法还能用吗?答案是否定的,换成了多维数组后上边的方法已经不能适用。
2、对象的拷贝
对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
let z = { a: 3, b: 4 }; let n = { ...z }; n // { a: 3, b: 4 }
DeepCopy: 深拷贝
深拷贝的实现一般是递归属性遍历
一般来说,在JavaScript中考虑复合类型的深层复制的时候,往往就是指对于Date、Object与Array这三个复合类型的处理。我们能想到的最常用的方法就是先创建一个空的新对象,然后递归遍历旧对象,直到发现基础类型的子节点才赋予到新对象对应的位置。不过这种方法会存在一个问题,就是JavaScript中存在着神奇的原型机制,并且这个原型会在遍历的时候出现,然后原型不应该被赋予给新对象。那么在遍历的过程中,我们应该考虑使用hasOenProperty方法来过滤掉那些继承自原型链上的属性:
function deepClone(obj) { var copy; if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = deepClone(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }
我们来验证一下,如下代码
let arr = [1,[2,4],3] let a = deepClone(arr)//这里deepClone函数是上边写的那个深度复制函数 a[0]=100 console.log(arr) console.log(a)
结果如下图