转载

JavaScript深拷贝初探

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

0. 为什么要实现深拷贝

在之前的一篇文章中   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,这就是深拷贝的意义所在。完整的拷贝一个对象的所有属性,而不是引用地址。

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

1. 数据类型判断

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

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

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

对于基本类型的判断,我们使用typeof就可以,对于实例类型,也可以通过instanceof来判断。

除了这两个方法以外,我们还有一些别的方式来判断,就是Object下的toString方法.

偷个懒,从MDN上查询下该方法的调用:

JavaScript深拷贝初探

也就是说,我们只需要截取返回值的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:100 3     }; 4  5      6     var obj2=util.deepClone(obj1);    //将obj1深拷贝到obj2 7     obj2.attr=200;    //修改obj2的属性值 8     console.log(obj1.attr);    //obj1属性值未发生变化 

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

正文到此结束
Loading...