你的位置:首页 > Java教程

[Java教程]JavaScript深拷贝初探


 

  今天和大家一起分享在JavaScript中如何实现深拷贝。

 

  0. 为什么要实现深拷贝

  在之前的一篇文章中  JavaScript变量存储浅析(二) 我们已经知道,在JS中,如果只是将一个对象简单的赋值给另外一个对象,那么拷贝的实际上只是对象在堆内存中的地址而已,也就是说,拷贝后的对象仍然和源对象指向同一个内存中的对象,只是修改其中一个对象,那么另外一个对象也会随之被修改。

  我们通过一个简单的例子来解释这个问题:

1 var obj1={2     attr:1003   };4   var obj2=obj1;  //简单复制obj1对象5   obj2.attr=200;  //修改obj2属性6   console.log(obj1.attr);  //输出200,说明obj1也被修改

  OK,这就是深拷贝的意义所在。完整的拷贝一个对象的所有属性,而不是引用地址。

  数组是这样的情况吗?大家可以自行验证一下!

  

  1. 数据类型判断

  实现深拷贝的第一步就是判断数据类型。

  我们都知道,JavaScript的数据类型分为两大类:

  •   基本类型:String,Number,Boolean,undefined,Null
  •   引用类型:Object,Array,Date,Reg,Function等

  对于基本类型的判断,我们使用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个字符开始截取到倒数第二个字符作为返回值。

  这个方法大家可以实际操作和验证一下。

 

  2. 深拷贝

  一般情况下,我们主要解决以下引用类型的深度拷贝:

  • 对象:遍历对象的所有属性,将其值拷贝到目标元素的对应属性上。
  • 数组:遍历数组的所有元素,将其值分别拷贝到目标数组的对象index下。
  • 函数:一般来说不作特殊处理,如果需要的话可以先将function通过tostring方法转换为字符串,然后再调用eval还原函数。

  本文的重点在于对象与数组的深度拷贝上。

 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方法用于实现深拷贝,需要传入源对象作为参数。

  • 第7行:拿到source源对象的类型。
  • 第8行:如果类型为数组的话,我们就创建一个空数组;如果是对象的话,创建一个空对象;然后将创建的空对象或空数组赋值到destination这个局部变量上。如果不是这两种类型的话,那就不属于深拷贝的范围,我们直接将源对象的值赋值回去。
  • 第9-10行:使用for..in对源对象进行循环,遍历其所有元素或属性。
  • 第11行:同样的,根据每个属性值的类型,在destination创建一个对应的空对象或空数组。
  • 第12行:使用callee进行函数的递归调用,再次计算每个属性或元素的值。
  • 第17行:返回局部变量destination。

  整个的实现核心就在于我们要清楚需要处理哪些类型的数据,以及使用callee进行递归调用

  好的,下面我们来使用深拷贝方法,看能否达到想要的效果:

1 var obj1={2     attr:1003   };4 5   6   var obj2=util.deepClone(obj1);  //将obj1深拷贝到obj27   obj2.attr=200;  //修改obj2的属性值8   console.log(obj1.attr);  //obj1属性值未发生变化

  最终的结果是:通过深拷贝得到的新对象在内存中有独立的存储位置,因此修改新对象不会对源对象造成任何影响。