深入Javascript对象比较

一 概述
 
1.1 类型
 
Javascript数据类型分为值类型引用类型 , 其中值类型包括字符串实体(例如:”string”),数值实体(例如:100)和布尔值实体(如:true)。而其他的复杂类型都属于引用类型,例如日 期型(new Date()),正则表达式(/a/gi, new RegExp(“a”, “gi”))数组([1,2,3], new Array())函数(function(){}, new Function())和对象({a:”a”, b:100}, new Object())。这些都是Javascript固有的数据类型,而用户自定义类型都属于引用类型(如:var Person = function(name){this.name=name;};),它们都只能使用new关键字实例化为具体对象(new Person(“hotoo”))。
 
为了理解Javascript对象的类型,我们来看一些如下代码:
// test instanceof and typeof:  
var instStr = [ '"string"' , '100' , 'true' , '/a/g' , '[1,2,3]' , 'function(){}' , '{}' , 'null' , 'undefined' ];  
var inst = [ "string" , 100 , true , /a/g , [ 1 , 2 , 3 ], function (){}, {}, null , undefined ]; 
var ObjsStr = [ "String" , "Number" , "Boolean" , "RegExp" , "Array" , "Function" , "Date" , "Object" ];  
var Objs = [ String , Number , Boolean , RegExp , Array , Function , Date , Object ];  
jsoutInst ( "instanceof" , ObjsStr, Objs , instStr , inst );  
 
 
 
function jsoutInst( methodName , tsStr , ts , osStr , os ){  
    document. write ( "<table border='1'><tr>" );  
    document. write ( "<td><strong>" + methodName + "</strong></td>" );  
    for ( var i = 0 ; i < tsStr . length ; i ++){  
       document. write ( "<td>" + tsStr [ i ]+ "</td>" );  
    } 
    document. write ( "</tr>" );  
    for ( var i = 0 ; i < os . length ; i ++){  
       document. write ( "<tr><td>" + osStr [ i ]+ "</td>" );  
       for ( var j = 0 ; j < ts . length ; j ++){  
           document. write ( "<td>" +( os [ i ] instanceof ts [ j ]? "<strong>true</strong>" : "false" )+ "</td>" );  
       } 
       document. write ( "</tr>" );  
    } 
    document. write ( "</table><br />" );  
}  
 
var typesStr = [ "string" , "number" , "boolean" , "array" , "function" , "date" , "object" , "undefined" ]; 
jsoutType ( "typeof" , typesStr, instStr , inst );  
function jsoutType( methodName , tsStr , osStr , os ){  
    document. write ( "<table border='1'><tr>" );  
    document. write ( "<td><strong>" + methodName + "</strong></td>" );  
    for ( var i = 0 ; i < tsStr . length ; i ++){  
       document. write ( "<td>" + tsStr [ i ]+ "</td>" );  
    } 
    document. write ( "</tr>" );  
    for ( var i = 0 ; i < os . length ; i ++){  
       document. write ( "<tr><td>" + osStr [ i ]+ "</td>" );  
       for ( var j = 0 ; j < tsStr . length ; j ++){  
           document. write ( "<td>" +( typeof os [ i ] == tsStr [ j ]? "<strong>true</strong>" : "false" )+ "</td>" );  
       } 
       document. write ( "</tr>" );  
    } 
    document. write ( "</table><br />" );  
}
上面的代码很简单,第一个函数判断对象实例是否是某个类的实例(instanceof),第二个函数对比对象实例是否与某类型(typeof)相等,他们将输出两个表格,我们来对比一下(为了方便阅读,这里将说明插在相应表格下。 )。

 

 

 

instanceof StringNumberBooleanRegExpArrayFunctionDateObject
"string"falsefalsefalsefalsefalsefalsefalsefalse
100falsefalsefalsefalsefalsefalsefalsefalse
truefalsefalsefalsefalsefalsefalsefalsefalse
/a/gfalsefalsefalse true falsefalsefalse true
[1,2,3]falsefalsefalsefalse true falsefalse true
function(){}falsefalsefalsefalsefalse true false true
{}falsefalsefalsefalsefalsefalsefalse true
nullfalsefalsefalsefalsefalsefalsefalsefalse
undefinedfalsefalsefalsefalsefalsefalsefalsefalse
 

 

通 过(instanceof)这个表格可以看出,值类型(”string”, 100, true等)不是任何对象的实体(instance),而引用类型(/a/g, [1,2,3], function(){}和 {})既是本身类型的实体,又是其父类型的实体(所有类型都继承自Object类型)。
所有通过引用类型(包括值类型的wrapper类String, Number和 Boolean)new出来的对象,都是其对应类和其父类(这里是Object)的实例。
虽然值类型不是其对应wrapper类的实例,但是值类型却可以直接使用其wrapper类的属性和方法,就如同值类型是其wrapper类的实例一样。例如:”ABC”.toLowerCase()。
 

 

typeof stringnumberbooleanarrayfunctiondateobjectundefined
"string" true falsefalsefalsefalsefalsefalsefalse
100false true falsefalsefalsefalsefalsefalse
truefalsefalse true falsefalsefalsefalsefalse
/a/gfalsefalsefalsefalsefalsefalse true false
[1,2,3]falsefalsefalsefalsefalsefalse true false
function(){}falsefalsefalsefalse true falsefalsefalse
{}falsefalsefalsefalsefalsefalse true false
nullfalsefalsefalsefalsefalsefalse true false
undefinedfalsefalsefalsefalsefalsefalsefalse true
 
而 从(typeof)表可以看出,使用typeof关键字可以知道值类型对应的wrapper类,而奇怪的是,引用类型的type都是”object”,要 判断这些类型时,我们只能通过instanceof关键字。特殊的,function(){}是个例外,关于function的特殊性,在本文后面也会遇 到。
所有引用类型(包括值类型的wrapper类)new出来的对象的type都是”object”。
对比两个表中的null和undefined两个特殊值发现,null的类型(type)是”object”,但是它不是Object的实例。而undefined的类型(type)是undefined,不是Object的实例。
注:以上是 Microsoft Server 2003, Internet Explorer 6.0 中的输出结果,在其他浏览器中测试结果一致。  
1.2 比较
 
比较必须具有一下性质:对称性,传递性,自等性。关于自等性,Javascript的NaN比较特殊,它不等于自身,包括同一个引用。
在Javascript中有两个基本符号用来比较对象:==和===。
1.2.1 等于==
 
下面是对《深入学习Javascript开发与实例》的引用:
等于操作符比较操作符左右两边的值,如果相等则返回true;否则就返回false。
对于除了Javascript 1.2以外的所有版本,在比较值大小之前会将其类型转为同一种数据类型。在进行类型转换时候,Javascript遵循下列规则:
l   true转换为数组1,false转换为数字0;
l   若两边表达式均为NaN,等于操作符返回false;
l   空值(null)和没定义(undefined)相等;
l   空值和没定义不等于0(零)、""、或false;
l   若比较字符串和数字,将字符串转为数字,再检查是否相等;
l   若比较对象和字符串,将对象转为字符串,再检查是否相等;
l   若比较对象和数字,将对象转为数字,再检查是否相等;
l   若符号两边均为对象,检查其地址是否相等。
 
1.2.2 完全相同===
 
下面是对《深入学习Javascript开发与实例》的引用:
比较操作符左右两边的值,如果相等,运算中则返回true;否则就返回false。
在比较完成之前,不进行类型转换。
 
1.2.3 扩展方法equals
 
在Java中的==号与Javascript相似,比较对象的内存地址。对于比较两个对象来说,==和===两个符号似乎没有什么意义,它们只能比较处是否是同一个对象,而我们要比较的是两个对象的值是否相等。
在Java中,基类Object的equals方法也是比较两个对象的地址,所以为了方便对象间的比较,一般要求用户创建的类都实现(覆写)equals方法。
为 了方便Javascript对象比较,我扩展了Javascript核心值类型和引用类型的equals方法,并且建议用户在创建类时也实现(覆写)这个 方法。由于在实现上,我没有将Object对象的equals方法简单的用==号或者===号比较,所以如果用户不覆写equals方法,也同样可以用 equals方法比较两个对象的相等性。

 

二 实现代码
 
下 面是所有的equals.js的代码,它可以方便你对复杂的引用类型进行值比较。要说明的是,因为在Object对象的equals方法中也同样调用了 equals方法,它可能是引用类型的equals方法,也可能是值类型的equals方法,递归至调用其他核心类型的equals方法。
这里的方法最“核心”的就是Object和Array两个类的equals方法,他们用到了类似递归,但不是递归的方法,最终退出“递归”是在调用其他核心对象的非递归equals方法时返回。
 
/*<![CDATA[*/
/** equals
  * @description 比较两个对象是否相等时 ,
  * 需要穿插使用其他对象的 equals 方法 ,
  * 这里将 Javascript 核心对象的 equals 方法扩展集合于此 ,
  * 用于方便实现其他对象的 equals 方法 ,
  * 建议所有用户创建类中都实现 equals 方法 .
  * 该方法在比较数组等对象时尤为有用 .
  * @create 2007-10-2
  * @update
  * @author 闲耘 (hotoo.cn[ AT ] gmail.com)
  */
 
 
/** 比较当前对象与指定对象是否相等。
  * 覆写并扩展基类 (Object) ,方便子类 ( 如: Array) 扩展此方法。
  * 这个方法可能用到 string, number, function 等基本数据类型的 wapper (String, Number, Function) equals 方法。
  * @param obj, Object.
  * @return Boolean.
  */
Object . prototype . equals = function ( obj ){
    if (( this === obj )){ return true ;}
    if (!( obj instanceof Object ) || ( obj === null )){ return false ;} // null is not instanceof Object.
    var i = 0 ; // object property counter.
    for ( var k in this ){
        i++;
        var o1 = this [ k ];
        var o2 = obj [ k ];
       if (( o1 != null ) && !( o1 . equals ( o2 ))){ return false ;} // inner object.
    }
    for ( var k in obj ){ // compare object property counter.
        i--;
    }
    return i === 0 ;
};
 
 
/** 比较当前函数对象与指定对象的值是否完全相等(包括数据类型)。
  * 函数的比较比较复杂和怪异,两个构造完全一致的函数的 valueOf() 值并不相同,这个可以理解。
  * 而使用 toString() 方法,是否也应该先将他们的无效空格和换行去掉?似乎问题变得复杂了。
  * 最大的问题是, new Function() function() {} toString() 方法在不同浏览器中表现不同,详情附注。
  * 出于简单性,一致性和函数的特殊性考虑,函数仅且仅在和本身比较时才相等。
  * @param number, Number.
  * @return Boolean.
  */
Function . prototype . equals = function ( fun ){
    return ( fun instanceof Function )&&
       (this . valueOf ()=== fun . valueOf ()); // new Function().valueOf() is not equals new Function().valueOf().
};
// 附注:
//=========================================
//(this.toString()==fun.toString());
//!important:function(){} new Function() toString 方法在不同浏览器中具有不确定性:
//=========================================
// IE6:new Function().toString():
//   function anonymous() {
//  
//   }
//- - - - - - - - - - - - - - - - - - - - -
// IE6:function(){}.toString():
//   function(){}
//=========================================
// FF1:new Function().toString():
//   function anonynous() {
//   }
//- - - - - - - - - - - - - - - - - - - - -
// FF1:function(){}.toString():
//   function () {
//   }
//=========================================
//   Opera9:new Function().toString():
//   function ()
//   {
//     }
//- - - - - - - - - - - - - - - - - - - - -
//   Opera9:function(){}.toString():
//   function ()
//   {
//     }
//==========================================
 
 
 
/** 比较当前字符串对象与指定对象是否相等。
  * @param string, String, Object.
  * @return Boolean.
  */
String . prototype . equals = function ( string ){
    return (( string instanceof String ) ||
       (typeof ( string )=== "string" ))&&
       (this . valueOf ()=== string . valueOf ());
};
 
 
/** 比较当前数字对象与指定对象是否完全相等(包括数据类型)。
  * @param number, Number.
  * @return Boolean.
  */
Number . prototype . equals = function ( number ){
    return (( number instanceof Number ) ||
       (typeof ( number )=== "number" )) &&
       (this . valueOf ()=== number . valueOf ());
};
 
 
/** 比较当前布尔对象与指定对象的值是否完全相等(包括数据类型)。
  * @param bool, Boolean.
  * @return Boolean.
  */
Boolean . prototype . equals = function ( bool ){
    return (( bool instanceof Boolean ) ||
       (typeof ( bool )=== "boolean" )) &&
       (this . valueOf ()=== bool . valueOf ());
};
 
 
/** 当前日期对象与另一日期对象的值相比较。
  * @param date, Date. 相比较的日期对象。
  * @return Boolean. 两个日期值是否相等。
  * @create
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值