一 概述
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 | String | Number | Boolean | RegExp | Array | Function | Date | Object |
"string" | false | false | false | false | false | false | false | false |
100 | false | false | false | false | false | false | false | false |
true | false | false | false | false | false | false | false | false |
/a/g | false | false | false | true | false | false | false | true |
[1,2,3] | false | false | false | false | true | false | false | true |
function(){} | false | false | false | false | false | true | false | true |
{} | false | false | false | false | false | false | false | true |
null | false | false | false | false | false | false | false | false |
undefined | false | false | false | false | false | false | false | false |
通 过(instanceof)这个表格可以看出,值类型(”string”, 100, true等)不是任何对象的实体(instance),而引用类型(/a/g, [1,2,3], function(){}和 {})既是本身类型的实体,又是其父类型的实体(所有类型都继承自Object类型)。
所有通过引用类型(包括值类型的wrapper类String, Number和 Boolean)new出来的对象,都是其对应类和其父类(这里是Object)的实例。
虽然值类型不是其对应wrapper类的实例,但是值类型却可以直接使用其wrapper类的属性和方法,就如同值类型是其wrapper类的实例一样。例如:”ABC”.toLowerCase()。
typeof | string | number | boolean | array | function | date | object | undefined |
"string" | true | false | false | false | false | false | false | false |
100 | false | true | false | false | false | false | false | false |
true | false | false | true | false | false | false | false | false |
/a/g | false | false | false | false | false | false | true | false |
[1,2,3] | false | false | false | false | false | false | true | false |
function(){} | false | false | false | false | true | false | false | false |
{} | false | false | false | false | false | false | true | false |
null | false | false | false | false | false | false | true | false |
undefined | false | false | false | false | false | false | false | 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