浅拷贝
复制一层对象的属性,并不包括对象里面的为引用类型的数据,当改变拷贝的对象里面的引用类型时,源对象也会改变。
深拷贝
重新开辟一个内存空间,需要递归拷贝对象里的引用,直到子属性都为基本类型。两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。
javaScript的变量类型
(1)基本类型:
5种基本数据类型Undefined、Null、Boolean、Number 和 String,变量是直接按值存放的,存放在栈内存中的简单数据段,可以直接访问。
(2)引用类型:
存放在堆内存中的对象,变量保存的是一个指针,这个指针指向另一个位置。当需要访问引用类型(如对象,数组等)的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。
简述两种类型的存储方式:
a. 基本类型(深拷贝)--名值存储在栈内存中,例如let a=1;
栈内存 | |
a | 1 |
当b=a复制时,栈内存会开辟一个内存
栈内存 | |
a | 1 |
b | 1 |
所以当你此时修改a=2,对b并不会造成影响。当然,let a=1,b=a;虽然b不受a影响,但这也算不上深拷贝,因为深拷贝本身只针对较为复杂的object类型数据。
b.引用数据类型(浅拷贝)--名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值
栈内存 | 堆内存 | ||
name | val | val | |
a |
堆地址1 |
[0,1,2] |
当b=a进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。
而当我们a[0]=1时进行数组修改时,由于a与b指向的是同一个地址,所以自然b也受了影响,这就是所谓的浅拷贝了。
浅拷贝的实现
1)简单的引用复制
function shallowClone(copyObj) { var obj = {}; for (var i in copyObj) { obj[i] = copyObj[i]; } return obj; } var x = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var y = shallowClone(x); x.b.f.g = 6 console.log(y.b.f.g); // 6
2)Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象
let obj1 = { a: 0, b: { c: 0 } }; let obj2 = Object.assign({}, obj1); console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}} obj2.b.c = 3; console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 3}} console.log(JSON.stringify(obj2)); // { a: 2, b: { c: 3}}
深拷贝的实现
1)Array的slice和concat方法
Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。之所以把它放在深拷贝里,是因为它看起来像是深拷贝。而实际上它是浅拷贝。
let a = [0, 1, [2, 3], 4], b = a.slice(); a[0] = 1; a[2][0] = 1; console.log(a); // [1,1,[1,3],4] console.log(b); // [0,1,[1,3],4]
从以上示例中可以看出拷贝的不彻底,b对象的一级属性确实不受影响了,但是二级属性还是没能拷贝成功,仍然脱离不了a的控制,说明slice根本不是真正的深拷贝。
第一层的属性确实深拷贝,拥有了独立的内存,但更深的属性却仍然公用了地址,所以才会造成上面的问题。
同理,concat方法与slice也存在这样的情况,他们都不是真正的深拷贝,这里需要注意。
2)利用递归复制所有层级属性,实现深拷贝
function deepClone(obj) { let objClone = Array.isArray(obj) ? [] : {}; if (obj && typeof obj === "object") { for (key in obj) { if (obj.hasOwnProperty(key)) { //判断ojb子元素是否为对象,如果是,递归复制 if (obj[key] && typeof obj[key] === "object") { objClone[key] = deepClone(obj[key]); } else { //如果不是,简单复制 objClone[key] = obj[key]; } } } } return objClone; } let a = [1, 2, 3, 4] b = deepClone(a); a[0] = 2; console.log(a);// [2,2,3,4] console.log(b);// [1,2,3,4]
3)jQuery.extend()方法源码实现
$.extend( [deep ], target, object1 [, objectN ] )
deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
object1 objectN可选。 Object类型 第一个以及第N个被合并的对象。
let a = [0, 1, [2, 3], 4], b = $.extend(true, [], a); a[0] = 1; a[2][0] = 1; console.log(a)// [1,1,[1,3],4] console.log(b)// [0,1,[2,3],4]
注意:jQuery的extend方法使用基本的递归思路实现了浅拷贝和深拷贝,但是这个方法也无法处理源对象内部循环引用,例如:
var a = { "name": "aaa" }; var b = { "name": "bbb" }; a.child = b; b.parent = a; $.extend(true, {}, a);//直接报了栈溢出。Uncaught RangeError: Maximum call stack size exceeded
4)JSON对象的parse和stringify
JSON对象是ES5中引入的新的类型(支持的浏览器为IE8+),JSON对象parse方法可以将JSON字符串反序列化成JS对象,stringify方法可以将JS对象序列化成JSON字符串,借助这两个方法,也可以实现对象的深拷贝。
obj1 = { a: 0, b: { c: 0 } }; let obj3 = JSON.parse(JSON.stringify(obj1)); obj1.a = 4; obj1.b.c = 4; console.log(JSON.stringify(obj3)); // { a: 0, b: { c: 0}} console.log(JSON.stringify(obj1)); // { a: 4, b: { c: 4}}