鸟食轩

 Microsoft .NET[C#] MVP 2003
随笔 - 429, 文章 - 235, 评论 - 5529, 引用 - 356
数据加载中……

兼容值类型的JavaScript对象Clone方法

    对象的克隆是OOP中常用的一个方法,可是JavaScript的对象(各种数据类型都是)却没有提供一个Clone方法。上次我实现了一个Clone方法,可是一直有个小毛病,就是遇到"值类型"就晕菜了。你或许会觉得值类型也Clone,搞笑吧?可是JavaScript动态类型语言,其实你在调用时可能更本不知道它是什么类型,能克隆"值类型"将会使代码具有很好的兼容性。

    当然由于我们可以使用typeof方法获取对象的基本类型,然后一个switch,该干嘛干嘛不就搞定了吗?这个... 我希望我的Clone能不去判断每种具体的类型,而尽量使用JavaScript语言提供的机制来完成,这也是上次那个Clone方法不能Clone值类型的原因。

    昨天麻袋同学给出了一个很棒的解决值类型Clone问题的方法,使用代码:
 objClone = new this.constructor(this.valueOf());
    代替我原来的:
 objClone = new this.constructor();

    这样一来值类型Clone的问题迎刃而解,原理(麻袋解释的):
 对于 date, boolean, number 这三种基本的数据类型,执行 
 var objClone 
= new this.constructor(); 
 的时候,得到的是默认值。而真实的值又没有属性可引用到,所以,只能在初始化的时候,以参数形式赋默认值。 
 var objClone 
= new this.constructor(this.valueOf()); 

    不过后来麻袋发现它的代码处理Object的时候有问题,于是给出了:
 var objClone; 
 
if ( this.constructor == Object ) 
    objClone 
= new this.constructor(); 
 
else 
    objClone 
= new this.constructor(this.valueOf()); 

    不能克隆Object的理论依据(from Script56.chm):
    obj = new Object([value]) 
    参数
    obj 
    必选项。要赋值为 Object 对象的变量名。 
    value 
    可选项。任意一种 JScript 基本数据类型。(Number、Boolean、或 String。)如果 value 为一个对象,返回不作改动的该对象。如果 value 为 null、undefined,或者没有给出,则产生没有内容的对象。
 
    上面红字中的"对象",因该指的是:new Object()这样的实例,用代码来翻译就是:
  var obj = new Object();
  obj.asdf 
= 'asdf';
  obj2 
= new Object(obj);
  alert(obj
===obj2);

    结果是啥?true!

    我在原来那个Clone的测试中,设计我的测试代码的第一个数据类型时,出了点错误,我用Literal的语法方式写的obj其实是Object的实例。本来我是想测试用户定义类型,应该这么弄:
  function JSClass()
  {
      
this.abc = 'abc';
      
this.faint = ['f', 'a', 'i', 'n', 't'];
      
this.toString = function()
      {
          
return 'abc:' + this.abc + ', faint:[' + this.faint + ']';
      };
  }
  
var jsClass = new JSClass();
  
var classClone = jsClass.Clone();
  
var classClone2 = jsClass.Clone();
  classClone2.abc 
= 'def';
  
for ( var i=0 ; i < classClone2.faint.length ; ++i ) classClone2.faint[i] = '-'; 
  Render('JSclass', jsClass, classClone, classClone2, 
true);
 
     这个JSClass也是适合使用new this.consturctor(this.valueOf())这种方式来Clone,这时的参数不会影响它生成的对象的。

     结合麻袋的修改,兼容版的JavaScript Clone方法出来了
 // Authors Birdshome, 麻袋@博客园  
 
Object.prototype.Clone = function()
 {
    
var objClone;
    
if ( this.constructor == Object ) objClone = new this.constructor(); 
    
else objClone = new this.constructor(this.valueOf()); 
    
for ( var key in this )
    {
        
if ( objClone[key] != this[key] )
        { 
            
if ( typeof(this[key]) == 'object' )
            { 
                objClone[key] 
= this[key].Clone();
            }
            
else
            {
                objClone[key] 
= this[key];
            }
        }
    }
    objClone.toString 
= this.toString;
    objClone.valueOf 
= this.valueOf;
    
return objClone; 
 }    

    至于需要不要把Object的默认方法都copy一遍,我也说不清楚,就暂时copy这两个最容易被重写的吧,不然要列一大排地说。

    测试结果附图:
   JScriptClone2.gif
    测试代码:
    <script language="javascript"></script>

posted on 2005-03-20 15:15 birdshome 阅读(3758) 评论(14)  编辑 收藏 网摘 所属分类: Jscript&Dhtml开发

评论

#1楼   回复  引用  查看    

我总在怀疑着,
JScript里Object的属性constructor.
是不是专门为实现clone()而准备的.
很少直接使用到constructor,
然而它却让你毫不费劲地实现prototype pattern

2005-03-20 17:42 | supersnake      

#2楼   回复  引用  查看    

我有一个有智商低下之嫌的问题不知道哪里问才好,
就是为什么进入我的博客网后,
我的每个随笔是"收敛"着的,里面一点内容也没有.
而不像其它人的会显示那么几行出来.
2005-03-20 18:03 | supersnake      

#3楼   回复  引用    

@supersnake
这里其实需要把JavaScript对象的constructor和OO语言中的构造器分开。面向对象语言中的Class是个模版,new这个模版来生成对象。而JavaScript中的constructor是个对象(Function的实例),只是它又具有OO语言中构造器的作用,可以被new操作。

// 写文章时写一下摘要,就是你看到的那个两三行的简介。
2005-03-20 18:12 | birdshome

#4楼   回复  引用  查看    

谢谢.
今天想了想,
觉得值的克隆还不是严格的,
因为克隆前后的 typeof 是不一样的.
var objClone;
var objInt = 3;
objClone 
= objInt.clone();
document.write(
"<br>typeof objInt:"+typeof objInt);
document.write(
"<br>typeof objClone:"+typeof objClone);
办法大概是
//
Object.prototype.clone = function(){ 
       
if(this.constructor==Number
    
|| this.constructor==String
    
|| this.constructor==Boolean)
    
return this.valueOf();

    
var newobj;
    newobj 
= new this.constructor();
        
    
for(var key in this)
    {
        
if(newobj[key] != this[key])
            newobj[key] 
= this[key].clone();
    }        
    newobj.toString 
= this.toString;
    newobj.valueOf 
= this.valueOf;
    
return newobj;
}
2005-03-21 09:53 | supersnake      

#5楼   回复  引用  查看    

然而, 新的问题出来了.
就是当你
new Number() 或者
new String() 或者
new Boolean(),
又调用 clone 时,
typeof 又不一样了,
哈哈,哭笑不得.
虽然这样用的时候不多.
而我偏偏不能在clone里用typeof 判断进来的东西是什么
2005-03-21 10:52 | supersnake      

#6楼   回复  引用  查看    

To: supersnake

写完这些以后,我感觉我们是在别人所指定的一个框架内实现框架外的东西。这个框架貌似很灵活,但是有时我反而觉得是有些问题它没有考虑到。没有考虑到造成没有约束,最终造成了灵活。
------------------------------
至于你最后说的那些类型,如果想完全Clone,那只能是重写这些类型的Clone方法。甚至有些对象是不应该有Clone方法的。
举个例子:
假如我的一个对象里包含一个Global对象,应该Clone吗?
2005-03-21 12:22 | 麻袋      

#7楼   回复  引用  查看    

Number.prototype.Clone = function(){
return this.valueOf();
}

Boolean......
......

------------------------------------
var i = 1;
var j = i.Clone();

当i执行Clone方法之前,i已经变成Number类型的对象了。
所以Clone的结果应该就是Number类型的对象。
2005-03-21 12:36 | 麻袋      

#8楼[楼主]   回复  引用  查看    

嗯,值类型或Build-in的对象确实应该单独Clone。

不过关于 1 和 new Number(1),或许有办法判断的:
因为:1 == new Number(1) 结果是:true;
而:1 === new Number(1)结果是:false。
就算我们统一Clone,出那么个差异因该也可以接受哈:)
2005-03-21 13:21 | birdshome      

#9楼   回复  引用    

版主先生,我很早就请您评论我的克隆方法了...看来我还不够资格!!
开个玩笑。
你的的克隆方法是不全面的。以后您会发现的
值类型或Build-in的对象本身就不能被克隆。
2005-03-21 16:39 | cmbscqhd[未注册用户]

#10楼   回复  引用    

@cmbscqhd
这么说就伤感了哦,没有采纳你的Clone方式我是解释了的:
当然由于我们可以使用typeof方法获取对象的基本类型,然后一个switch,该干嘛干嘛不就搞定了吗?

关于值类型的Clone,其实JavaScript也存在C#的boxing和unboxing的相似概念,就是1和new Number(1)却别的问题。

后来的讨论,更集中在了兼容值类型的同时,让代码最大可能的精简,清晰。
2005-03-21 19:15 | birdshome

#11楼[楼主]   回复  引用  查看    

@麻袋
应该说有些过分的灵活,给我带来了凌乱,就像Build-in的对象Math,其实也是有constructor的,只是:
new Math.constructor得到的对象不再具有那些默认的方法。

@cmbscqhd
补充说一点,如果对于Build-in Object,一定要:
var m = Math;
alert(m.sin(1));
这么来用,我觉得似乎是bt了点,虽然不能拒绝。
2005-03-21 19:30 | birdshome      

#12楼   回复  引用  查看    

我刚开始想Clone的时候,曾经使用过重写一些类的valueOf方法。中间我忘了是什么原因了,后来就一直没有按这个思路走。
然后又想到了,给Object加一个Clone方法,但仅仅是
Object.prorotype.clone = function(){
return new Object();
}
再在每个想让其具备Clone能力的类中重写这个方法。但有些类或对象是不应该有这些方法的,比如你所说的Math, 还有RegExp等。还有就是这些值类型,它们本身就不是对象,即便能写出来实现这样功能的方法,也没有办法使用,因为一使用,它本身就变成对象了。
最后,曾经想过将Clone定义为全局的方法,即:
function Clone(variable){
......
return varResult;
}
但是,还是觉得不舒服。有种要脱离OO的感觉。

现在,只能是根据具体的项目来使用不同的思路了。
2005-03-21 21:23 | 麻袋      

#13楼   回复  引用    

我曾经写过一个post(准确的说是和一个朋友的讨论),也提到了clone方法,利用这个clone,你可以实现prototype模式

http://dev.csdn.net/article/22/22843.shtm
也不知道我的理解是否正确
2005-03-21 23:43 | Eric Liu[未注册用户]

#14楼[楼主]   回复  引用  查看    

@Eric Liu
看了你的文章,也测试了你的Clone代码,结果很是不幸。使用我上面给出的测试代码,除了Error对象外,其它全都通不过测试。主要一个原因是你的new CloneModel()实际上破坏了被克隆对象的类型,特别是Array对象,它被克隆后变成Object的实例了,虽然被克隆对象的属性都可以被expando方式导入。
 
Data TypeOriginalClonedModifiedStatus
Objectabc:abc, faint:[f,a,i,n,t]abc:abc, faint:[]abc:def, faint:[]:(:(:(
JSclassabc:abc, faint:[f,a,i,n,t]abc:abc, faint:[]abc:def, faint:[]:(:(:(
Arraya,r,y:(:(:(
Array(null):(:(:(
Stringfaintlayala:(:(:(
Booleantruefalse:(:(:(
Number10010000:(:(:(
Error100, error!100, error!10000, ERROR!:):):)

BTW: CloneModel使用内嵌类的方式,太晦涩了,可能很多人都昏迷湖的。
2005-03-22 00:31 | birdshome      



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 122246




相关文章:

相关链接: