我知道的深浅拷贝
深拷贝和浅拷贝
-
浅拷贝:浅拷贝创建一个新的对象,这个对象的属性值和原始对象的属性值有一份精准拷贝,如果属性值是基本类型,那克隆的就是基本属性的值,如果属性值是引用类型,那克隆的就是其内存地址。
-
深拷贝:深拷贝是从堆内存中开辟出一段新的区域,来放置拷贝的新对象。
// 浅拷贝
let obj = {
a: 1,
b: {
c: 1
}
}
let obj1 = Object.assign({}, obj)
obj1.b.c = 2
obj1.a = 2
console.log(obj); // { a: 1, b: { c: 2 } }
总而言之,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
赋值,浅拷贝和深拷贝
三者对于引用类型的区别:
- 赋值:赋值只是将引用类型存放在栈中的指针,赋值给一个给一个新的对象,这两个对象具有相同的内存指针,因此改变其中一个对象的属性值,两个对象的值都会改变。
let obj = {
a: 1,
b: {
c: 1
}
}
let obj1 = obj
obj1.a = 2
obj1.b.c = 2
console.log(obj); /// { a: 2, b: { c: 2 } }
-
浅拷贝:浅拷贝会重新在堆内存中开辟区域,拷贝的原始类型值互不影响,但是引用类型还是指向同一块地址类似赋值。代码见第一段代码
-
深拷贝:深拷贝也会重新在堆内存中开辟新的区域,而且深拷贝中属性值为引用类型,也会重新开辟一段地址来存放这个引用类型,因此深拷贝的两个对象互相不影响。
let obj = {
a: 1,
b: {
c: 1
}
}
let obj2 = JSON.parse(JSON.stringify(obj))
obj2.b.c = 3
console.log(obj); // { a: 1, b: { c: 1 } }
console.log(obj2); // { a: 1, b: { c: 3 } }
浅拷贝的实现方法
1.Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
let obj = {
a: 1,
b: {
c: 1
}
}
let obj1 = Object.assign({}, obj, {d: 1})
console.log(obj1); // { a: 1, b: { c: 1 }, d: 1 }
2.解构
ES6新增解构运算符
let obj = {
a: 1,
b: {
c: 1
}
}
let obj1 = {...obj}
obj1.b.c = 2
console.log(obj); // { a: 1, b: { c: 2 } }
console.log(obj1); // { a: 1, b: { c: 2 } }
3.Array.prototype.concat()
let arr = [1, 2, { name: 'xiaoniaoe'}]
let arr1 = [].concat(arr)
arr1[2].name = 'eee'
console.log(arr); // [ 1, 2, { name: 'eee' } ]
4.Array.prototype.slice()
let arr = [1, 2, { name: 'xiaoniaoe'}]
let arr1 = arr.slice()
arr1[2].name = 'eee'
console.log(arr); // [ 1, 2, { name: 'eee' } ]
深拷贝的实现方法
1. JSON.parse(JSON.stringify(obj))
先将对象转为JSON格式字符串,再将其转换回来,从而来达到深拷贝的作用。
缺点:
-
1、对象中有字段值为
undefined,转换后则会直接字段消失 -
2、对象如果有字段值为
RegExp对象,转换后则字段值会变成{} -
3、对象如果有字段值为
NaN、+-Infinity,转换后则字段值变成null
let obj = {
a: 1,
b: {
c: 1
},
d: function() {}
}
let obj1 = JSON.parse(JSON.stringify(obj))
console.log(obj1); // { a: 1, b: { c: 1 } }
2. 自行封装
我们首先要知道深拷贝需要达到的效果是什么样的:
- 生成的是一个新的对象,有自己独立的内存地址
- 拷贝的对象可能会存在嵌套对象
- 对undefined,正则,function等这些特殊值的拷贝
- 对环引用的解决
解决问题:
- 创建一个新的空对象,将原对象的值循环复制过来
- 使用递归调用
- 环引用,使用map
function deepclone(target, map = new Map()) {
// 递归出口,判断是否是对象
if(typeof target !== 'object'){
return target
}
// 判断是数组还是对象
const temp = Array.isArray(target) ? [] : {}
// 判断是否以及存在,环引用
if(map.get(target)) {
return map.get(target)
}
map.set(target, temp)
// 循环递归遍历
for(const item in target) {
temp[item] = deepclone(target[item], map)
}
return temp
}
对于不遍历的属性
对于function等不可遍历的属性,就手动的判断一下类型,再进行复制就可以了。 需要声明正则的克隆方法,symbol的声明方法等等。
总结
多动手,多实践。