js对象深度克隆


什么是深度克隆,通俗的说:就是在给你一个对象的前提下,你创建一个和这个对象一模一样的对象,所有属性和值都相等,并且修改新对象的属性不会影响旧的对象。

网上常见的两种比较简洁的方式:
方式1:这个方式像极了我们的原型继承,所以我暂时叫他原型式克隆
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 )) 


你可能感兴趣的:(前端开发)