关于JavaScript深浅拷贝的理解
2018-04-04 12:41
295 查看
上一篇内容提到了基本类型值和引用类型值在复制,传递时的不同表现。由此也引出了这篇文章的内容--深浅拷贝。这处就有了三个概念:赋值,浅拷贝,深拷贝。
首先,我们说到的深浅拷贝是针对引用类型的。基本类型值复制后得到的是两个互不相关的独立个体,一个值的改变不会影响到另一个。而引用类型复制后,两个变量保存的都是指向同一个对象的“指针”。基于这个特性,也才会引出深浅拷贝的话题。那么,字面上理解浅拷贝是否就是引用类型通过引用复制得到的结果了?例如下面这样的:
验证下得到结果:
阅读更多
首先,我们说到的深浅拷贝是针对引用类型的。基本类型值复制后得到的是两个互不相关的独立个体,一个值的改变不会影响到另一个。而引用类型复制后,两个变量保存的都是指向同一个对象的“指针”。基于这个特性,也才会引出深浅拷贝的话题。那么,字面上理解浅拷贝是否就是引用类型通过引用复制得到的结果了?例如下面这样的:
var obj = { name : "jack" }; var copy_obj = obj; //copy_obj 是否就是我们所说的浅拷贝?
带着这个疑问,先查一下深浅拷贝的定义,所谓浅拷贝就是新增加一个“指针”指向已有的对象,深拷贝就是在浅拷贝的基础上,新开辟一个内存空间,使新增的“指针”指向新的内存空间保存的对象。类比下就是浅拷贝相当于起了个小名,深拷贝就是多莉羊。根据上面的叙述,似乎copy_obj 就是obj的浅拷贝。
暂时先这么理解着,接下来思考如何实现深拷贝。对,遍历对象啊。把key-value逐个取出来放到一个新对象中,我们先试一下:
var obj = { name : 'jack', age : 12, score : [1,2,3] }; var copy_obj = {}; (function copy(obj){ for(var k in obj){ if(obj.hasOwnProperty(k)) copy_obj[k] = obj[k]; } })(obj);这样看起来我们应该是得到两个对象了,下面做一下测试:
obj.age = 13; copy_obj.age //12 obj.score.push(4); copy_obj.score //[1,2,3,4]也就是说,我们上面做的遍历赋值只是在 var copy_obj = obj 的基础上前进了很小的一步。再回顾一下:
var obj = { name : "jack" }; var copy_obj = obj; obj.name = 'Alice'; copy_obj.name //Alice所以,两者的区别在于,我们的function确实生成了一个新对象,但是新对象copy_obj中value为引用类型的值的指向和原对象obj中的指向是一致的。也就是说:
obj.score === copy_obj.score由此,我们对之前关于赋值和浅拷贝的疑问有了一个答案,结果就是引用类型的赋值是浅拷贝中的一种。再去思考深拷贝的实现,上面写的function已经完成了创建一个新对象,缺少的就是让对象中的“子对象”也独立出来。思路有了,我们需要对“子对象”也进行遍历。因此,我们遇到了一个新问题,如何判断对象中value的类型。首先,我们看下Js内置的引用类型:
- Object
- Array
- Date
- RegExp
- Function
- 基本包装类型
其中“基本包装类型”在我们这里是不用考虑的。在上一篇关于Js变量的文章里面我们提到了一个方法:
Object.prototype.toString.bind(obj)().slice(8, -1);做个测试:
var obj = { a : {}, b : [1,2], c : new Date(), d : /^a$/, e : function(){ console.log('hello'); } } function valueType(obj){ var result = []; for(var k in obj){ if(obj.hasOwnProperty(k)){ result.push(Object.prototype.toString.bind(obj[k])().slice(8, -1)); } } return result; } var result = valueType(obj); //["Object", "Array", "Date", "RegExp", "Function"]OK,第一步解决。然后想一下对每种类型怎么处理,如果是Object,我可以新建一个var a = {},然后对内部再遍历;如果是Array,可以新建一个var b = [],然后再遍历放值;如果是Date,先看下obj.c是什么:
obj.c //Wed Apr 04 2018 10:41:22 GMT+0800 (中国标准时间)既然是对象,那就有toString()方法,我们先让它以字符串形式返回,然后以其作为参数,再去创建一个Date,大概就是下面这样:
var str = obj.c.toString(); var newDate = new Date(str); newDate //Wed Apr 04 2018 10:41:22 GMT+0800 (中国标准时间)同样的,对于RegExp,是不是都能这样,尝试一下:
var reg = obj.d.valueOf(); var newReg = new RegExp(reg); newReg // /^a$/ newReg == obj.d // false没有问题,那Function呢,在js中,new Function(arg1,arg2,....,function_body),前面argx是函数参数,function_body是函数主体,并且都得是字符串形式,好像实现起来有点麻烦啊,强行去这么操作也是可以实现的,那就要拼接字符串了,暂且先不考虑它了吧(留下了没技术的眼泪。。。)
现在可以汇总一下了,把上面的内容整合一下,试着写一个深拷贝方法,直接贴代码:
var obj = { a : {}, b : [1,2,new Date(),{},/^a$/], c : new Date(), d : /^a$/, e : function(){ console.log('hello'); } }; function deepClone(obj){ //拷贝对象 var copy_obj; //判断类型 var type = Object.prototype.toString.bind(obj)().slice(8, -1); //Object if(type === 'Object'){ copy_obj = {}; }else if(type === 'Array'){ copy_obj = []; }else if(type === 'Date'){ copy_obj = new Date(obj.valueOf()); }else if(type === 'RegExp'){ copy_obj = new RegExp(obj.valueOf()); }else{ //如果是基本类型值或者type === 'Function' return obj; } //遍历 if(type === 'Object'){ for(var k in obj){ if(obj.hasOwnProperty(k)){ copy_obj[k] = deepClone(obj[k]); } } }else if(type === 'Array'){ var i = 0, len = obj.length; for(;i<len;i++){ copy_obj.push(deepClone(obj[i])); } } return copy_obj; } var copy_obj = deepClone(obj);
验证下得到结果:
obj.a == copy_obj.a //false obj.b == copy_obj.b //false obj.b[2] == copy_obj.b[2] //false obj.b[3] == copy_obj.b[3] //false obj.b[4] == copy_obj.b[4] //false obj.c == copy_obj.c //false obj.d == copy_obj.d //false obj.e == copy_obj.e //true和前面说的一样,只有Function没有完全独立出来。这个方法暂时就到这里。
我查找深拷贝资料的时候看到提及比较多的就是JSON反解析。具体方法如下:
var obj = { a : {}, b : [1,2,new Date(),{},/^a$/], c : new Date(), d : /^a$/, e : function(){ console.log('hello'); } }; var copy_obj = JSON.parse(JSON.stringify(obj)); copy_obj //{a: {…}, b: Array(5), c: "2018-04-04T04:30:07.954Z", d: {…}}结果是Chrome的给出来的,我们可以看到,JSON反解析存在以下几个问题:
- Date类型得到的结果是字符串形式
- RegExp类型得到的结果是一个空对象
- Function类型没办法转换,直接被去掉了
我们看下过程,第一步把对象转换成JSON对象:
var obj = { a : {}, b : [1,2,new Date(),{},/^a$/], c : new Date(), d : /^a$/, e : function(){ console.log('hello'); } }; JSON.stringify(obj) // "{"a":{},"b":[1,2,"2018-04-04T04:34:29.667Z",{},{}],"c":"2018-04-04T04:34:29.667Z","d":{}}"可以看出来在这一步就已经出问题了,这也是用JSON做深拷贝的局限性,如果我们需要深拷贝的对象不包含Date,RegExp,Function,用JSON反解析就很方便了。反之还是一个一个检查类型做出相应的操作从而实现深拷贝。
关于深拷贝,还有一些其他方法,在这就不展开了,上面文字的叙述顺序就是我当时思考深拷贝这个问题时的大体思路。记录下来供大家参考,欢迎大家指正交流。
阅读更多
相关文章推荐
- 关于:1.指针与对象;2.深浅拷贝(复制);3.可变与不可变对象;4.copy与mutableCopy的一些理解
- JavaScript关于作用域、作用域链和闭包的理解
- 关于非容器类和容器类深浅拷贝的区分
- 关于 Javascript 的闭包理解
- 关于javascript中this的理解
- javascript 数组的深浅拷贝
- 关于JavaScript中立即执行函数的理解
- javascript 关于闭包的解释与理解
- JavaScript小知识点(一):深浅拷贝
- 关于客户端javascript的理解及事件浅析
- 关于javascript中this关键字(翻译+自我理解)
- JavaScript之深浅拷贝
- javaScript 关于console的理解
- 关于javascript中Prototype的理解
- 关于setTimeout,理解JavaScript定时机制
- 深入理解关于javascript中apply()和call()方法的区别
- Javascript 深拷贝与浅拷贝理解与应用
- javascript 对象深浅拷贝的解决方案
- 关于javascript中闭包的理解
- javascript深浅拷贝