今天和大家一起分享在JavaScript中如何实现深拷贝。
在之前的一篇文章中 JavaScript变量存储浅析(二) 我们已经知道,在JS中,如果只是将一个对象简单的赋值给另外一个对象,那么拷贝的实际上只是对象在堆内存中的地址而已,也就是说,拷贝后的对象仍然和源对象指向同一个内存中的对象,只是修改其中一个对象,那么另外一个对象也会随之被修改。
我们通过一个简单的例子来解释这个问题:
1 var obj1={ 2 attr:100 3 }; 4 var obj2=obj1; //简单复制obj1对象 5 obj2.attr=200; //修改obj2属性 6 console.log(obj1.attr); //输出200,说明obj1也被修改
OK,这就是深拷贝的意义所在。完整的拷贝一个对象的所有属性,而不是引用地址。
数组是这样的情况吗?大家可以自行验证一下!
实现深拷贝的第一步就是判断数据类型。
我们都知道,JavaScript的数据类型分为两大类:
对于基本类型的判断,我们使用typeof就可以,对于实例类型,也可以通过instanceof来判断。
除了这两个方法以外,我们还有一些别的方式来判断,就是Object下的toString方法.
偷个懒,从MDN上查询下该方法的调用:
也就是说,我们只需要截取返回值的type值就可以了。下面是一个参考方法:
1 var util={ 2 getType:function(o){ //判断对象类型 3 var _t; 4 return ((_t = typeof(o)) == "object" ? o==null && "null" || Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase(); 5 } 6 };
我们先定义了一个util对象用于存放本节需要使用的相关方法,getType方法用于检测对象类型。
实现的原理上面也提及了,如果是基本类型的话,就直接返回typeof值。如果是对象类型,我们还需要进行细分,从第8个字符开始截取到倒数第二个字符作为返回值。
这个方法大家可以实际操作和验证一下。
一般情况下,我们主要解决以下引用类型的深度拷贝:
本文的重点在于对象与数组的深度拷贝上。
1 var util={ 2 getType:function(o){ //判断对象类型 3 var _t; 4 return ((_t = typeof(o)) == "object" ? o==null && "null" || Object.prototype.toString.call(o).slice(8,-1):_t).toLowerCase(); 5 }, 6 deepClone:function(source){ //深拷贝 7 var destination=this.getType(source); 8 destination=destination==='array'?[]:(destination==='object'?{}:source); 9 for (var p in source) { 10 if (this.getType(source[p]) === "array" || this.getType(source[p]) === "object") { 11 destination[p] = this.getType(source[p]) === "array" ? [] : {}; 12 destination[p]=arguments.callee(source[p]); 13 } else { 14 destination[p] = source[p]; 15 } 16 } 17 return destination; 18 } 19 };
我们在util对象上添加了deepClone方法用于实现深拷贝,需要传入源对象作为参数。
整个的实现核心就在于我们要清楚需要处理哪些类型的数据,以及使用callee进行递归调用 。
好的,下面我们来使用深拷贝方法,看能否达到想要的效果:
1 var obj1={ 2 attr:100 3 }; 4 5 6 var obj2=util.deepClone(obj1); //将obj1深拷贝到obj2 7 obj2.attr=200; //修改obj2的属性值 8 console.log(obj1.attr); //obj1属性值未发生变化
最终的结果是: 通过深拷贝得到的新对象在内存中有独立的存储位置,因此修改新对象不会对源对象造成任何影响。