对象深拷贝

最近在项目中用到了对象拷贝这一块,而且用到的是对象的深拷贝。下面就让我们来看一下关于对象的拷贝–浅拷贝和深拷贝。
先看一下深拷贝和浅拷贝的区别(概念)

JS 中的浅拷贝与深拷贝,只是针对复杂数据类型(Object,Array)的复制问题。浅拷贝与深拷贝都可以实现在已有对象上再生出一份的作用。但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此拷贝的时候就存在两种情况了:拷贝引用和拷贝实例,这也是浅拷贝和深拷贝的区别。

浅拷贝:浅拷贝是拷贝引用,拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响;
深拷贝:在堆中重新分配内存,并且把源对象所有属性都进行新建拷贝,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原来的对象是完全隔离,互不影响;

我原先用的是ES6中的Object.assign()方法,发现它只能做一些简单的属性拷贝。用代码说话:

let obj1 = {a:1,b:2,c:3};
let obj2 = {};
Object.assign(obj2,obj1)
//  obj2  {a: 1, b: 2, c: 3}
obj2.a = 9;
obj1.a    //  1

从上可以看出基本数据类型的属性的拷贝是没有问题的
那么我们来看一下如果含有对象或数组这样的属性时呢?

let obj3 = {a:1,b:2,c:3,d:{d1:1,d2:2}};
let obj4 = {};
Object.assign(obj4,obj3)
{a: 1, b: 2, c: 3, d: {…}}
obj4.d.d1 = 9;
obj3.d.d1  // 9

可以看出这个时候修改obj4中d对象属性中的值得时候,obj3中也会跟着改变,这说明Object.assign()在拷贝对象或数组这样的属性时是属于浅拷贝的。
那么当我们需要用到深拷贝对象的时候,怎么办呢?有这样几种方法
1.常见的JSON.stringfy()和JSON.parse() 上面的obj3和obj4用这个方法试一下看效果如何

let obj3 = {a:1,b:2,c:3,d:{d1:1,d2:2}};
let temp = JSON.stringify(obj3);
let obj4 = JSON.parse(temp);
obj4 // {a:1,b:2,c:3,d:{d1:1,d2:2}};
obj4.d.d1 = 10;
obj3.d.d1;  // 9

可以看到经过JSON.stringfy()和JSON.parse()的转化就可以进行深拷贝了
但是这个是有缺陷的,请看代码

let obj3 = {a:1,b:2,c:3,d:{d1:1,d2:2},e(){console.log('函数')}}
let temp = JSON.stringify(obj3);
let obj4 = JSON.parse(temp);
obj4 // {a:1,b:2,c:3,d:{d1:1,d2:2}}

结果可以看到obj4中丢失了e()这个函数类型 ,由此可知对象中含有Function类型的方法时,使用JSON.stringfy()和JSON.parse()是没有办法拷贝的,同时也包括正则对象,symbol类型,看下面的例子

let sym = Symbol('sym');
let obj12= {a:/[^\d\.-]/g,b:sym}
let temp2 = JSON.stringify(obj12);
let obj13 = JSON.parse(temp2)
obj13 // {a:{}}

可以看到当对象中含有正则表达式的属性时,使用JSON.stringfy()和JSON.parse()是进行深拷贝时,结果得到的是一个空对象。
所以通过JSON.stringify实现深拷贝有几点要注意
1.拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
2.无法拷贝不可枚举的属性,无法拷贝对象的原型链
3.拷贝Date引用类型会变成字符串
4.拷贝RegExp引用类型会变成空对象
5.对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null
6.无法拷贝对象的循环应用(即obj[key] = obj)

那么有没有更好的方法可以解决上述的问题呢?有,有这样几种方法可以解决上述问题以实现对象的深拷贝。且容我慢慢总结实践一下。

2.迭代递归法
这个也是比较常用的方法,可以和方法1进行对比记忆

function deepClone(obj){
    const cloneObj = obj.constructor === Array ? [ ] : { }  // 判断要克隆的数据是对象还是数组
    for(let key in obj){
       if (obj.hasOwnProperty(key)){ // 判断key是否为自身的属性(let in 有时候会把原型上的属性也遍历出来,下面会介绍)
             if(obj[key] && typeof obj[key] === 'object'){ // 判断该属性是否是对象或者数组
                  cloneObj[key] = obj[key].constructor === Array ? [ ] : { } ;
                  cloneObj[key] = deepClone(obj[key]) // 递归调用 (自己内部调用自己)
             }else{
                 cloneObj[key] = obj[key]
             }
       }
    }
    return cloneObj;
}
// 实际应用一下
let originObj = {a:1,b:{c:1,d:2,e:{f:2}},g:[1,2,3,4,5],h(){console.log(1234)}}
originObj
//  {a: 1, b: {c:1,d:2,e:{f:2}}, g: Array(5), h: ƒ}
let targinObj = deepClone(originObj)
targinObj
//  {a: 1, b: {c:1,d:2,e:{f:2}}, g: Array(5), h: ƒ}
targinObj.b.e.f = 9
// 9
originObj.b.e.f
// 2

可以看到使用上面的方法当嵌套多层的时候和包含函数属性的时候都可以进行深拷贝的。
这个方法在实际开发中是可行的,只不过如果包含Symbol类型,正则表达式等特殊的类型时,还需要对这个方法进行扩展。深拷贝更深级版将在方法3中揭晓,敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值