说明: 本篇主要讨论JavaScript中各运算符对运算数进行的类型转换的影响,本文中所提到的对象类型仅指JavaScript预定义的类型和程序员自己实现的对象,不包括宿主环境定义的特殊对象(比如浏览器定义的对象)
上一篇中讨论了JavaScript中原始类型到原始类型的转换,原始类型到对象类型的转换和对象类型到原始类型的转换,这里先提出一个问题
var a = undefined; if(a){ console.log('hello'); }else{ console.log('world'); } console.log(a == false);
以前我不对JavaScript的一知半解的时候,我以为上面的代码回先后输出'world'和'true',但是经过执行代码,发现结果是'world'和'false'.undefined确确实实可以被转换为false,但是在使用==操作符对undefined和false两个值进行相等性比较时,由于两个操作数的类型不同,这里会发生类型转换,并且undefined并没有被转换为boolean类型然后跟false比较,所以本篇讨论下运算符对运算数类型转换的影响.
1.+(二元),==,!=,>,<,<=,>=
二元+运算符用于把两个数字相加或者连接两个字符串,所以该运算符期望的运算数类型为数字或者字符串。当运算数都是数字或者字符串时,结果是显而易见的,否则的话就会现对操作数进行类型转换,把操作数转换为数字或者字符串,然后进行数学相加或者连接字符串的运算。操作数会照下面的规则进行类型转换:
1.如果其中任一操作数是对象类型,先执行对象到原始类型的转换:对于Date对象,通过调用toString方法得到原始类型的值;对于其他对象,优先尝试valueOf方法,然后再尝试toString方法,如果valueOf和toString都无法得到原始类型的值,则会抛出异常
2.现在两个操作数都是原始类型了,如果其中一个操作数是字符串,则把另外一个原始类型的操作数转换成字符串,然后执行字符串连接运算
3.否则,把两个操作数都转换成数字进行数学加法运算
console.log('10'+null);//10null console.log('10'+undefined);//10undefined console.log('10'+false);//10false console.log('10'+true);//10true console.log('10'+10);//1010 var o1 = {}; console.log('10'+o1);//10[object Object] 对象默认的valueOf方法返回的不是原始类型,所以会调用toString方法 o1.toString = function(){ return 'o1'; } console.log('10'+o1);//10o1 o1.valueOf = function(){ return null; } console.log('10'+o1);//10null 对象现在有了一个返回原始类型的valueOf方法,不再调用toStringconsole.log(10+null); console.log(10+undefined);//NaN, undefinde转换成数字是Nan console.log(10+false);//10 console.log(10+true);//11 var o1 = {}; console.log(10+o1);//10[object Object] 对象默认的valueOf方法返回的不是原始类型,所以会调用toString方法,而toString返回的是字符串,所以执行字符串连接 o1.toString = function(){ return true; } console.log(10+o1);//11 o1.valueOf = function(){ return null; } console.log(10+o1);//10 对象现在有了一个返回原始类型的valueOf方法,不再调用toString var date = new Date; console.log(date + true);//Tue Aug 19 2014 10:32:49 GMT+0800 (中国标准时间)true
使用==运算符比较两个运算数的相等性时,如果两个运算数类型不一致,就会发生类型转换,转换时遵循以下规则
1.null和undefined是相等的
2.如果一个操作数是数字,另一个操作数是字符串,则把字符串转换成数字再进行比较
3.如果有布尔类型的操作数,把它转换成数字
4.如果其中一个操作数是对象类型,把对象类型转换成原始类型再进行比较.当把对象转换成原始值时,优先使用valueOf方法,其次使用toString方法,但是与+运算符一样,Date对象是个特例,Date对象调用toString方法
5.其余情况一律认为不想等
到这里可以解释本文开始提出的问题了,undefined与false进行==比较,false先被转换成数字0,然后与undefined比较,属于情况5,返回false
console.log(0 == null);//false console.log( 0 == undefined);// false console.log(0 == false);//true,false转换成0 console.log(1 == true);//true,true转换成1 console.log(true == '1');//true,true先转换成1,然后1转换成字符串'1' console.log(null == 'null');//false,情况5 var o = {}; console.log(o == '[object Object]');//true,toString方法返回'[object Object]' o.valueOf = function(){ return '1'; }; console.log(true == o);//true,true先转换为1,o先调用valueOf得到一个原始值'1',数字与字符串比较,数字转换成字符串
对于!=运算符,结果总是与==运算符的结果相反
对于>,<(很萌有木有),<=,>=这四个比较运算符,期望的运算数类型与二元+一样,要么比较两个数字,要么比较两个字符串,如果运算数不全位数字或字符串,则会发生类型转换,转换的规则如下:
1.如果任一操作数是对象类型,则先后尝试valueOf方法和toString方法把对象转换成原始类型,需要注意的是这里Date不再做特殊处理了,与其他对象一样
2.经过第一步之后,两个操作数都是原始类型了,如果都是字符串,则进行字符串比较,如果都是数字,则进行数字比较
3.否则,把两个原始类型都转换成数字,然后进行比较
console.log(1 > null);//true,null->0 console.log( 1 >= undefined);// false,undefined->NaN console.log(1 <= undefined);//false console.log(null > undefined) // false,null->0,undefinde->NaN console.log(null <= undefined)//false console.log('1' < true);//false,'1'->1,true->1 console.log('1' > true);//false var o = {}; o.toString = function(){ return 10; }; console.log(o > 9);//true, o->10 console.log(o > '9')//true,o->10,'9'->9 o.toString = function(){ return '10'; } console.log(o > 9);//true, o->'10','10'->10 console.log(o > '9')//false,o->'10' o={}; var o1={}; console.log(o > o1);//o和o1都通过toString方法转换成了字符串,并且字符串内容是一致的 console.log(o < o1); console.log(o >= o1);
2.+(一元),-(一元和二元),*,++,--
这几个运算符期望的运算数都是数字类型,所以当操作数类型不对时,会把操作数转换为数字
var a = '10'; console.log(+a);//+10 console.log(1 - a);//-9 console.log(a++);//10,但是此时a的类型已经发生了变化,++和--都需要一个左值,所以++'10'是错误的 console.log(typeof a);//输出number,a的类型已经改变 a = '10'; var o = {}; o.toString = function() { return '10'; } console.log(o++);//10,o->'10'->10 console.log(typeof o);//number o = {}; o.toString = function() { return '10'; } o.valueOf = function(){ return false; } console.log(++o);//1,优先使用valueOf方法 console.log(typeof o);//number
3.总结
从本文的分析可以看出,JavaScript中的大部分运算符都只能对原始类型的操作数进行运算,其中一部分只能对数字类型进行运算,另一部分可以对数字和字符串类型进行运算,当进行运算时,运算数与预期的类型不相符合时,会通过先后调用valueOf和toString方法将对象类型转换为原始类型(需要注意Date对象的特殊性),然后再将原始类型转换为数字或者字符串.