博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
javascript对象的深度克隆
阅读量:6005 次
发布时间:2019-06-20

本文共 4700 字,大约阅读时间需要 15 分钟。

在做项目的时候需要向对象里面添加新属性,又不想修改原对象。于是就写: var newObj = oldObj,但是新对象属性改变后就对象也会跟着改变,这是因为无论是新对象还是旧对象,指向的内存地址都是一样的,改变了谁都改变了 内存中的数据。

于是找到了一个取巧的方法就是先把旧对象转化为字符串 然后 在转化为对象给新对象,虽然可以达到效果,但是总感觉有点不正规。于是想到了深度克隆

function cloneObjectFn (obj){     // 对象复制         return JSON.parse(JSON.stringify(obj))}
一、js中的对象
  谈到对象的克隆,必定要说一下对象的概念。
  js中的数据类型分为两大类:原始类型和对象类型。
  (1)原始类型包括:数值、字符串、布尔值、null、undefined
  (2)对象类型包括:对象即是属性的集合,当然这里又两个特殊的对象----函数(js中的一等对象)、数组(键值的有序集合)。
  好了既然对象分为这两类,这两种类型在复制克隆的时候是有很大区别的。原始类型存储的是对象的实际数据,而对象类型存储的是对象的引用地址(对象的实际内容单独存放,为了减少数据开销通常存放在内存中)。ps:大家要知道,对象的原型也是引用对象,它把原型的方法和属性放在内存当中,通过原型链的方式来指向这个内存地址。
 
二、克隆的概念
  浅度克隆:原始类型为值传递,对象类型仍为引用传递。
  深度克隆:所有元素或属性均完全复制,与原对象完全脱离,也就是说所有对于新对象的修改都不会反映到原对象中。
 
三、浅克隆的表现
1,原始类型
  看下面一段代码:
//数值克隆的表现var a="1";var b=a;b="2";console.log(a);// "1"console.log(b);// "2"
//字符串克隆的表现var c="1";var d=c;d="2";console.log(c);// "1"console.log(d);// "2"
//布尔值克隆的表现var x=true;var y=x;y=false;console.log(x);// trueconsole.log(y);// false

从上面的代码大家可以看出,原始类型即使我们采用普通的克隆方式仍能得到正确的结果,原因就是原始类型存储的是对象的实际数据。

2.对象类型
  函数式一等对象,当然也是对象类型,但是函数的克隆通过浅克隆即可实现
var m=function(){alert(1);};var n=m;n=function(){alert(2);}; console.log(m());//1console.log(n());//2
  大家能看到,直接通过普通赋值的方式,就实现了函数的克隆,并且不会影响之前的对象。原因就是函数的克隆会在内存单独开辟一块空间,互不影响。
 
  为了方便后续的代码表现,这里定义一个复杂的对象类型oPerson。下面看一下对象类型的浅复制有什么危害:
var oPerson={    oName:"rookiebob",    oAge:"18",    oAddress:{        province:"beijing"    },        ofavorite:[        "swimming",        {reading:"history book"}    ],    skill:function(){        console.log("bob is coding");    }};function clone(obj){    var result={};    for(key in obj){        result[key]=obj[key];    }    return result;}var oNew=clone(oPerson);console.log(oPerson.oAddress.province);//beijingoNew.oAddress.province="shanghai";console.log(oPerson.oAddress.province);//shanghai
  通过上面的代码,可以看到,经过对象克隆以后,修改oNew的地址,发现原对象oPerson也被修改了。这说明对象的克隆不够彻底,那也就是说深度克隆失败!
 
四、深克隆的实现
  为了保证对象的所有属性都被复制到,我们必须知道如果for循环以后,得到的元素仍是Object或者Array,那么需要再次循环,直到元素是原始类型或者函数为止。为了得到元素的类型,我们定义一个通用函数,用来返回传入对象的类型。
//返回传递给他的任意对象的类function isClass(o){    if(o===null) return "Null";    if(o===undefined) return "Undefined";    return Object.prototype.toString.call(o).slice(8,-1);}
  PS:Object.prototype.toString.call(o)能直接返回对象的类属性,形如"[object Number]"的字符串,通过截取class,并能知道传入的对象是什么类型。这里对象类型不做重点讲。
当然这里有两个疑问需要解释下:
  (1)为什么不直接用toString方法?这是为了防止对象中的toString方法被重写,为了正确的调用toString()版本,必须间接的调用Function.call()方法
  (2)为什么不使用typeof来直接判断类型?因为对于Array而言,使用typeof(Array)返回的是object,所以不能得到正确的Array,这里对于后续的数组克隆将产生致命的问题。
下面就是真正的深度克隆
//深度克隆function deepClone(obj){    var result,oClass=isClass(obj);        //确定result的类型    if(oClass==="Object"){        result={};    }else if(oClass==="Array"){        result=[];    }else{        return obj;    }    for(key in obj){        var copy=obj[key];        if(isClass(copy)=="Object"){            result[key]=arguments.callee(copy);//递归调用        }else if(isClass(copy)=="Array"){            result[key]=arguments.callee(copy);        }else{            result[key]=obj[key];        }    }    return result;}//返回传递给他的任意对象的类function isClass(o){    if(o===null) return "Null";    if(o===undefined) return "Undefined";    return Object.prototype.toString.call(o).slice(8,-1);}var oPerson={    oName:"rookiebob",    oAge:"18",    oAddress:{        province:"beijing"    },        ofavorite:[        "swimming",        {reading:"history book"}    ],    skill:function(){        console.log("bob is coding");    }};//深度克隆一个对象var oNew=deepClone(oPerson); oNew.ofavorite[1].reading="picture";console.log(oNew.ofavorite[1].reading);//pictureconsole.log(oPerson.ofavorite[1].reading);//history book oNew.oAddress.province="shanghai";console.log(oPerson.oAddress.province);//beijingconsole.log(oNew.oAddress.province);//shanghai
  从上面的代码可以看到,深度克隆的对象可以完全脱离原对象,我们对新对象的任何修改都不会反映到原对象中,这样深度克隆就实现了。
 
  这里要注意一点的就是:为什么deepClone这个函数中的result一定要判断类型?这里有一种情况,如果你的result直接是{}对象,明明传进去的是一个数组,结果你复制完了以后,变成了一个对象了。
//深度克隆function deepClone(obj){    var result={},oClass=isClass(obj);    // if(oClass==="Object"){    //     result={};    // }else if(oClass==="Array"){    //     result=[];    // }else{    //     return obj;    // }    for(key in obj){        var copy=obj[key];        if(isClass(copy)=="Object"){            result[key]=arguments.callee(copy);        }else if(isClass(copy)=="Array"){            result[key]=arguments.callee(copy);        }else{            result[key]=obj[key];        }    }    return result;}function isClass(o){    if(o===null) return "Null";    if(o===undefined) return "Undefined";    return Object.prototype.toString.call(o).slice(8,-1);}//克隆一个数组var arr=["a","b","c"];var oNew=deepClone(arr);console.log(oNew);//Object {0: "a", 1: "b", 2: "c"}

转载于:https://www.cnblogs.com/wangmaoling/p/8039190.html

你可能感兴趣的文章
2018年OpenStack用户调查报告出炉:Kubernetes仍居首
查看>>
Eclipse基金会发布Eclipse Photon IDE
查看>>
纯css实现左右横线,文字自适应居中效果
查看>>
唯品会HDFS性能挑战和优化实践
查看>>
JavaScript 设计模式
查看>>
Java EE供应商和伦敦Java用户组宣布新的MicroProfile
查看>>
PostgreSQL中的大容量空间探索时间序列数据存储
查看>>
敏捷制造:并不是你想像的矛盾体
查看>>
jQuery选择器和事件
查看>>
十、syslog日志与loganalyzer日志管理
查看>>
Python多进程并发写入PostgreSQL数据表
查看>>
mysql 优化
查看>>
2.4 salt grains与pillar jinja的模板
查看>>
简单的验证码程序
查看>>
MySQL主从(介绍,配置主机,配置从机,测试主从同步)
查看>>
不同版本的outlook客户端配置Office 365 exchange online帐户需要安装的补丁
查看>>
Java服务器-resin
查看>>
Linux下搭建JDK和TOMCAT环境
查看>>
关闭windows休眠
查看>>
Ansible之十一:变量详解
查看>>