什么是深度克隆,通俗的说:就是在给你一个对象的前提下,你创建一个和这个对象一模一样的对象,所有属性和值都相等,并且修改新对象的属性不会影响旧的对象。 网上常见的两种比较简洁的方式: 方式1:这个方式像极了我们的原型继承,所以我暂时叫他原型式克隆 |
<pre name="code" class="javascript">function clone1(obj1)
{
function F() {} ;
F.prototype = obj1 ;
var f = new F() ;
for(var key in obj1)
{
if(typeof obj1[key] =="object" && obj1[key]!=null)
{
f[key] = arguments.callee(obj1[key])
}
}
return f ;
}
克隆之后,对象属性全部变成了原型里面的属性。似乎从结构上,不同于原来的对象了。也不能通过hasOwnProperty判断属性的是继承属性还是常规属性.但保存实例关系(instanceof)。
方式2:这个方式采用的是构造函数实现的,我暂时叫他构造函数式克隆
function clone2(obj1)
{
var o;
if(obj1.constructor==Object)
{
o = new obj1.constructor() ;
}
else
{
o = new obj1.constructor(obj1.valueOf())
}
for(var key in obj1)
{
if(obj1[key]!=o[key])//这句有很大的问题
{
if(typeof obj1[key] =="object")
o[key] = clone2(obj1[key]) ;
else
o[key] = obj1[key] ;
}
}
o.toString = obj1.toString;
o.valueOf = obj1.valueOf;
return o;
}
接下来我们一步步分析他们的优劣:
方式2我觉得问题比较多,先分析它,我们写一个测试:
function Obj1(){
this.a = 1 ;
this.b = 2 ;
}
var o = new Obj1() ;
alert(o.a) ;//打印1
alert(o.b) ;//打印2
var o1 = clone2(o) ;
alert(o1.a) ;//打印1
alert(o1.b) ;//打印2
o1.a = 3 ;
alert(o1.a) ;//打印3
alert(o.a) ;//打印1
似乎没什么问题,稍微修改一下
function Obj1(){
this.a = 1 ;
this.b = null ;
}
那么运行上面的结果将是: Cannot read property 'constructor' of null
问题在哪里呢?null这个值,既不是引用类型,也不是基本数据类型,但是 typoef null == "object"这个是成立的。
然而null却不是对象,所以没有constructor这个属性,程序报错!!
只需要把clone2的 if(typeof obj1[key] =="object") 改为 if(typeof obj1[key] =="object"&& obj1[key] !=null )
这个修改就完美了吗?我们再修改一下程序
function Obj1(){
this.a = 1 ;
this.b = 2 ;
}
Obj1.prototype.arr = [1,2,3,4] ;
var o = new Obj1() ;
alert(o.arr[3]) ;//打印4
var o1 = clone2(o) ;
alert(o1.arr[3]) ;//打印4
o1.arr[3] = 8 ;
alert(o1.arr[3]) ;//打印8
alert(o.arr[3]) ;//打印8
可以看到,我们修改克隆之后的对象的属性值 被克隆的对象也被修改了,这显然不是深度克隆的宗旨,深度克隆,即使引用类型的属性,也要重新在内存中申请,不能复制引用。问题出在哪里呢?
对就是那句 “这句有很大问题”: if(obj1[key]!=o[key])
首先我们要理解原型,原型是对象共享数据的地方,原型中的属性被称为继承属性,非原型中的属性是常规属性,继承属性中的引用类型是被所有对象共享的。
通过 o = new obj1.constructor(obj1.valueOf()) ,那么o和obj1拥有同样的原型,都继承了arr属性,由于arr是一个引用的类型,所以
obj1.arr==o.arr是成立的。所以执行到这里,clone2跳过了克隆arr,直接复制arr。
所以我觉得去掉这个 if(obj1[key]!=o[key]) 判断比较好。如果觉得这样每个属性都遍历一遍有点麻烦的话。
可以把 if(obj1[key]!=o[key]) 改为if(!obj1.hasOwnProperty(key)),就是说,如果是常规属性,那么就不必克隆了。
这里有个知识点:常规属性,通过new创建的对象,都是对象独立拥有的,包括引用数据类型。
方法3
/**
* 深度扩展对象--适用于对象的属性也是对象的情况
* @param {Object}
* @return {Object}
*/
var deepextend = function (destination, source) {
for (var property in source) {
var copy = source[property]; // 获取source属性值
if (destination === copy) {
continue;
}
// 如果copy是一个对象,则递归调用(并传入copy参数),直到copy不是一个对象为止
if (typeof copy === 'object' && copy != null) {//$.isObj(copy)
destination[property] = arguments.callee(destination[property] || {}, copy); //递归调用
// 否则直接把copy赋值给destination对象的属性(此时与$.extend方法等价)
} else {
destination[property] = copy;
}
}
return destination;
};
问题:当对象的属性为数组时,只能克隆属性名,没有克隆属性值。也不能通过hasOwnProperty判断属性的是继承属性还是常规属性.也不保存实例关系(instanceof)。
方式4:
function deepCopy(p, c) {
var c = c || {};
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
方法5一行代码实现纯数据json对象的深度克隆
var dataObjCloned=JSON.parse(JSON.stringify( dataObj ))