本文关键字
- 基本数据类型
- 对象类型
- 装箱操作
- 运算符重载
最近也少写东西了,今天看到网友 topcss 的评论
http://www.bgscript.com/archives/24#comment-159
有感,在分析我为什么要那样写之余,谈谈灵活的JavaScript语言系统中对”对象”类型的检测法.
JavaScript中的运算符重载:valueOf()
有些人可能会觉得很奇怪,JavaScript中没像C++语言中提供运算符重载这强大的功能,我想说的是,灵活的JavaScript也可做到运算符重载,虽然能力有限.
Object对象有个方法可能大家平时很少关注,其实这方法正是贯穿本文的方法,那就是valueOf(),且看例子,JS中怎么实现运算符重载.
obj.valueOf() : 返回指定对象的原始值,即对象求值,JavaScript中利用该值参与对象的运算操作.
alert(+ new Date()); alert( (new Date()).valueOf()); alert(new Date() + new Date());
在上面的语句中,JS引擎正是调用Date对象的valueOf求得值并参与算术运算符+的运算.
得知过程后,众所周知,两字符串间的+运算是字符串的连接操作,
alert(new String('string1') + new String('string2'));
假如我要重载字符串对象的运算符,使得字符串相加时是字符串长度相加而非连接,怎么做?可以这样,重写valueOf方法:
String.prototype.valueOf = function(){ return this.length; } //显示5 alert(new String('ss') + new String('www'));
有些同学可能觉得很奇怪了,为什么
alert('ss' + 'www');
还是显示 ’sswww’,
其实这也是正常的,因为’ss’,'www’这些是JS的基本类型,未经过装箱操作使得它们变成字符串对象实例,所以在+号运算时JS引擎还是执行默认的字符串连接操作.只要改改,使得基本数据类型在运算时JS引擎对其进行装箱操作后就可以了.
String.prototype.boxUp = function(){ //这里不对对像进行其它操作,就返回自身,即证明基本类型已被装箱 return this; }; // 下面这句就能正常如愿了,显示 5 alert('ss'.boxUp() + 'www'.boxUp()); //显示 6 alert('ss'.boxUp() * 'www'.boxUp());
好了,经过上面的讨论,有了结论:
JS中对象的所具有的行为与运算方式是通过方法来定义
回到话题,利用上面的方法我们可以构建一个”数字类”,且不讨论这”数字类”是否为一”数字”类.
我们参照Number类的特性,类可以进行算术运算符,逻辑运算符与字符串转换操作.
先定义好这类,名为 NumberB,与Number类一样,接受一个基本数字类型
function NumberB( num ){ this.value = Number(num); return this.value; }
再定义一个类,作为NumberB的”父类”,起桥接作用,暂且不管这桥接的作用,下文会说到.
function Bridge(){};
NumberB原型指向Bridge实例
NumberB.prototype = new Bridge();
之后再重载valueOf方法,使得NumberB类能参与运算符运算操作,与Number行为一致.
NumberB.prototype.valueOf = function(){ return this.value; };
再重载toString,进行正确的字符串转换.
NumberB.protoype.toString(){ return this.value + ''; };
现在,该类的行为与Number基本一致了,也可进行运算,可以测试一下:
var a = new NumberB(10), b = new NumberB(5); alert(a + b); alert(a / b); alert(a + b + ', hello');
好了,到目前为止,你可能还不大信息这类是一个”数字类”,基于JavaScript的灵活特性,下面将使得它变得更像点,那就是通过原型使得该类”继承”自Number类, 符合instanceof 原型链检测.
这里就用到上面的桥接类了,将上面的的桥椄类代码改进为:
注意下面的第四句,第四句类NumberB的原型被new Bridge()覆盖后,其原型的构造器属性已被覆盖,
在覆盖前为 alert(NumberB.prototype.constructor === NumberB);
最后一句将其修正.
1 2 3 4 5 6 7 | function Bridge(){}; // 该句使得类"继承"自Number Bridge.prototype = Number.prototype; NumberB.prototype = new Bridge(); //修正NumberB类的constructor属性,使其指向正确的NumberB NumberB.prototype.constructor = NumberB; |
上面将类NumberB”继承”自Number类后,符合了instanceof检测,
//显示 true alert( new NumberB('5') instanceof Number); //显示 NumberB alert( new NumberB('5').constructor);
好了,呵呵,现在你还怀疑NumberB不是一个”数字类”么.
再回到讨论的话题, 判断一个对象是否为”数字”,两个方法比较一下,将从两方面说说我为什么不采用obj.constructor来检测.
下面两个方法经笔者优化过,将==换成===.
//利用constructor检测
function isNumber(ob) {
return ob.constructor === Number;
}
//利用tyoeof 与 instanceof检测
function isNumberB(ob) {
return typeof ob === "number" || ob instanceof Number;
}
1. 对对象混合类型的判断方面
先弄清楚什么是数字类型?
笔者觉得作为数字类型的对象至少要满足其基本数据类型的行为,如对基本数据类型的运算操作等.
从这意义上去,在类型上面的NumberB与Number类是等价的,Number类是内置的对象,作为对基本数据类型的一个对象化封装,而NumberB是对其的另一个对象封装,不同的是,Number对象是JavaScript引擎默认的基本数据类型装箱对象,所以就往往采用ob.constructor === Number去检测,上面的NumberB例子已说明,
假如按特征来判断一个对象是否为数字类型的话, obj.constructor === Number是obj为数字类型的充分非必要条件.
如上面的NumberB类,有些对象通过原型的继承关系获得父类的大部份行为,
在上面NumberB例子中,按数字类型的特征判断,NumberB类可看作是数字类,isNumber方法不能检测,因为构造器却不是Number,此时,isNumberB方法能测出该对象是数字类.
var num = new NumberB(5); //false alert( isNumber(num) ); // true alert( isNumberB(num) );
2. 性能方面
下面具体分析这两个方法的性能.
传入的参数数据类型对性能有很关键影响.
参数有两种类型:
- 1. 基本数据类型, 如 func( 5 );
- 2. 对象类型 , 如 func(new Number(5));
对于函数:
function isNumber(ob) {
return ob.constructor === Number;
}
如果传进的是基本数据类型,
所要进行的操作有:
- 对基本数据类型的装箱操作
- 装箱成Number对象实例后,寻找原型链上的constructor属性
- 对象引用比较
如果传进的是对象类型
所要进行的操作有:
- 寻找原型链上的constructor属性
- 对象引用比较
所以对对象的操作少了装箱操作.
但日常我们用到的数字多是基本的数据类型,所以大多数情况下isNumber方法少不了装箱操作.
对于函数:
function isNumberB(ob) {
return typeof ob === “number” || ob instanceof Number;
}
如果传进的是基本数据类型,
所要进行的操作有:
- typeof ob 检测无需装箱
- 值比较
如果传进的是对象类型
所要进行的操作有:
- 如果为Number实例,引用比较返回true
- 如果非Number实例,如上面的NumberB类实例,将进行 instanceof 比较原型链
可见,对于对象类型而非Number的实例对象,isNumberB将进行更多的比较.
但日常我们用到的数字多是基本的数据类型,所以大多数情况下isNumberB方法将执行typeof检测后返回正确的.
可见,如果传进的是基本数据类型的话,无论如何,isNumberB都不会比isNumber慢,如果是对象类型或非数字类型的对象isNumberB有相当可能比isNumber慢.
下面是笔者在firefox,ie,chrome 下的测试数据,判断运行一百万次:
var n = new Number(5);
// n = new NumberB(5);
var d = (new Date()).valueOf();
for(var i=0;i<1000000;i++){
isNumber(n);
// isNumberB(n);
}
alert(new Date() - d);
// 结果
//firefox 3.5.3
isNumberB(n) 407 ms
isNumberB(5) 7 ms
//--
isNumber(n) 610 ms
isNumber(5) 1555 ms
// ie 8
isNumberB(n) 1110 ms
isNumberB(5) 813 ms
//--
isNumber(n) 970 ms
isNumber(5) 954 ms
//chrome
isNumberB(n) 51 ms
isNumberB(5) 38 ms
//--
isNumber(n) 53 ms
isNumber(5) 170 ms
额外话:
IE对基本数据类型的typeof检测,真是令我对IE的JS引擎”佩服”得五体投地,
一般JS引擎的基本数据类型数据结构如下:
struct Value {
//类型标记
int dataType;
Data *data;
...
}
对于typeof 检测,直接判断dataType取值即可,所以理应非常快的.这方面firefox做得最好,居然7ms就能完成任务.
但IE耗时居然和对象类型差不多,可能并没优化,一律进行装箱操作 -_-!!!