在JavaScript中,类型转换是无时无刻的存在的,下面先从简单开始,然后深入探讨这个问题。
首先是其他类型转换成布尔类型。在JavaScript中,如果一个变量的值为false、undefined、null、0、0.0、NaN和字符串长度为0的时候,转换成布尔值时得到的是false,如果是其他对象的话转换的是true。如:
var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t
a = false; //-->false
b = true; //-->true
c = 0; //-->false
d = -0; //-->false
e = 0.0; //-->false
f = NaN; //-->false
g = undefined; //-->false
h = null; //-->false
i = ''; //-->false
j = []; //-->true
k = {}; //-->true
l = function(){}; //-->true
m = new Array(); //-->true
n = 'null'; //-->true
o = 'undefined'; //-->true
p = 'false'; //-->true
q = 'null'; //-->true
r = '0'; //-->true
s = ' '; //-->true
t = 'NaN' //-->true
然后其他类型转换成数字类型。在JavaScript中,undefined、字符串不是纯数字和其他对象类型都会转换成NaN,true会转换成1,false会转换成0,null也是转换成0,如果字符串为纯数字,如果运算符为加号,因为加号连接的优先级比相加的优先级高,所以数字和字符串相加会连接起来返回的字符串类型,如果运算符是减乘除,则返回的是数字类型。详细的结果如下:
var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y
var z = 0
a = false-1; //number
b = true-1; //number
c = 0-1; //number
d = -0-1; //number
e = 0.0-1; //number
f = NaN-1; //isNaN number
g = undefined-1; //number
h = null-1; //number
i = ''-1; //number
j = []-1; //number
k = {}-1; //isNaN number
l = (function(){})()-1; //isNaN number
m = new Array()-1; //number
n = 'null'-1; //isNaN number
o = 'undefined'+1; //isNaN string
p = 'false'-1; //isNaN number
q = 'null'-1; //isNaN number
r = '0'-1; //number
s = ' '-1; //number
t = 'NaN'-1 //isNaN number
u = [1]-1 //number
v = {1:1}-1 //isNaN number
w = (function(){return 1})()-1 //number
x = new Array(1)-1 //number
y = '0'+1 //string
function ha(ev){
if (isNaN(ev)) console.log(++z + ' :isNaN' + ' :' + (typeof ev));
else console.log(++z + ' :' + (typeof ev))
}
相信眼尖的同学肯定看出来里面有些东西怎么跟自己想的不一样,下面我就单独拿出来一个一个讲。
首先是变量o和变量y,两个的类型都是string,但是一个还输出了NaN,另一个没有。按道理说,NaN是属于Number对象的,怎么会出现在String类型里面呢?假如我们将变量o写成o='undefined'-1,那么它的结果又和变量n的一样,这是为什么呢?其实呢,产生NaN的原因有两个,要么算术结果返回未定义的值要么算术结果返回一个无法表示的值,而o='undefined'-1正好属于这种类型,强制转换成数字类型,但是无法表示,只能返回NaN,但是数据类型还是Number。然而由于isNaN()函数在进行检测的时候强行把o='undefined+1'转换成Number类型来检验是否为NaN,很明显肯定是NaN,所以会得出y变量没有输出isNaN而o变量则输出了isNaN。有什么办法能够解决这个问题呢?在es6版本里,有Number.isNaN()方法,这个方法并不会进行强制转换,或者也可以用这个方法,x!=x,这种方法也比isNaN()方法靠谱。
然后变量i和变量s可能有同学也会有疑问了,为什么一个空字符减一个数字竟然还有值,甚至连一个有空格的字符串与数字相减还是有值,而不是NaN。首先呢,空字符与数字相减会被强制转换成0然后与数字相减。而变量s这里,变量s竟然等于-1,也就是说空格字符串等于0?但是在上面的例子中空格字符串可是返回true的。这是因为在进行算术运算的时候,空格字符串被强制的通过Number()方法进行转换,Number(' ')返回的结果是0,所以等到的是-1。那么好了,空字符串和空格字符串到底有什么区别呢?先看下面的例子:
console.log(' ' == false)
if (' ') console.log('y')
console.log('' == false)
if ('') console.log('y')
这里除了第一个返回的结果都相同外,后面的都不同,主要是空格字符串是有值的,并不是false值,在与Boolean和Number类型进行比较的时候被Number强制转换变成0。而空字符串是false值,这是JavaScript比较让人迷糊的一点。
除此以外还有一个迷惑的地方是变量j和u或者x。在这里空数组即长度为0的数组,它又Number强制转换后得出的结果是0,但是又不同于字符串,当数组长度为1的时候且里面的元素是数字或者是空格,都会转换成相应的数值。像['1']、[' ']、[1]、[]会相应的转换成1、0、1、0。但是如果里面是['false']或者[false]之类的,则转换成NaN。如果数组的长度超过1,则转换的结果都是NaN。最后套用《JavaScript权威指南》的话,这是因为数组到数字的转换需要调用toString()方法,转换成字符串后再转换成数字。
最后是其他类型转换为字符串型。像数字型、布尔型、undefined、null或者是NaN,转换成与之对应的值。如:
a = false; //'false'
b = true; //'true'
c = 0; //'0'
d = 1; //'1'
e = 1.23; //'1.23'
f = NaN; //'NaN'
g = undefined; //'undefined'
h = null; //'null'
i = []; //''
j = {}; //[object Object]
k = function(){}; //'function(){}'
l = new Array(); //''
m = [1] //'1'
n = {1:1} //'[object Object]'
o = function(){return 1} //'function(){return 1}'
p = new Array(1) //''
q = Object.create(null) //报错
r = [1,2,,] //'1,2,'
s = (function(){return 1})() //'1'
t = /\d+/g //'/\\d+/g'
相信大家最想了解的是对象和类是如何转换成字符串的。如变量i后面的所有变量。在JavaScript中,所有的对象都继承了两种转换方法,一个是toString(),另一个是valueOf()。toString()方法返回的是对象的字符串,所以数组类转换成字符串的时候是元素变成字符串并且元素与元素之间加逗号,如变量i、l、r,而我们的变量p和变量r中,虽然变量p的数组长度为1,但是数组内根本没有元素,用0 in p返回的结果是false,所以返回的是空字符串,而变量r在元素2后面有两个逗号,说明数组长度为3,然而没有元素存在,所以返回是空字符串。
而函数类则直接输出定义的函数,若该函数有返回值返回,则将返回值变成字符串。如变量k、o和s,这里变量s是因为直接运行并得到返回值,所以转换的是返回值。在正则表达式中则直接输出表达式内的内容,如变量t。而对象是复合值,如变量j和n,它不可能真正的表示一个原始值,所以toString()方法将输出对象类型的名称。至于为什么变量q会报错,原因是变量q这种声明对象的方法使得对象不继承任何属性和方法,所以这个对象没有toString()和valueOf(),所以不能够被转换成字符串类型。
valueOf()方法的意思是如果对象存在一个原始值,它就默认将对象转换为表示它的原始值。但是大多数对象都是复合值,所以valueOf()方法会将对象简单的转换为对象本身。最后通过下面的摘自《JavaScript权威指南》的一段话,来概况JavaScript中用String和Number方法把对象转为字符串和对象转为数字的过程:
js对象转字符串:如果对象具有toString()方法,则调用这个方法。如果它返回一个原始值,JavaScript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。如果对象没有toString()方法,或者这个方法并不返回一个原始值,那么JavaScript会调用valueOf()方法。如果存在这个方法,则JavaScript调用它。如果返回值是原始值,JavaScript将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果。否则JavaScript无法从toString()或valueOf()获得一个原始值,因此这时它将抛出一个类型错误异常。
js对象转数字的时候,不同于转字符串,会优先调用valueOf()方法:如果对象具有valueOf()方法,后者返回一个原始值,则JavaScript将这个原始值转换为数字(如果需要的话)并返回这个数字。否则,如果对象具有toString()方法,后者返回一个字符串,则JavaScript将字符串转换并返回数字类型。如果两个方法都没有,JavaScript抛出一个类型错误异常。
如下面重写一个对象里的valueOf和toString方法,看看有什么不同:
var a = {
valueOf : function(){
return 1
},
toString : function(){
return 3
}
}
console.log(a+'') //1
console.log(a+1) //2
console.log(String(a)) //3
console.log(Number(a)) //1
但是如果不重写valueOf()方法,则下面的会又输出什么:
var a = {
toString : function(){
return 2
}
}
console.log(a+'') //3
console.log(a+1) //4
console.log(String(a)) //3
console.log(Number(a)) //3
在JavaScript中,对象进行转换的时候都会优先用valueOf方法,如果valueOf返回的是一个引用类型而不是原始值,则调用toString方法,像上面的一样。但是如果是通过Number或者String等进行显式转换的话,则按照《JavaScript权威指南》的来做。
在JavaScript中,常见的隐式转换除了上面的这些外,还有下面这几个,如:“+”、“!”和“==”。
首先是“+”运算符,如果是一个数值变量和一个字符串变量中间有这个操作符的话,那么数值变量会转换成字符串,但是一个字符串如果里面都是数字,前面有个“+”号则被转换成数值类型。而“!”运算符的话,则将操作数转换成布尔值并取反 。如:
var a = 'aa'
var b = 1
var c =[]
var d,e,f
d = a + b
e = +b
f = !c
console.log(typeof d) //string
console.log(typeof e) //number
console.log(typeof f) //boolean
console.log(typeof !![]) //boolean
而“==”的转换中,如果是String、Boolean和Number类型之间进行比较时,如String和Number比较、Boolean和Number比较、String和Boolean比较,则都会转换为数值型。而如果是String或Number与Object进行比较的话,则Object通过toString()或者valueOf方法转换成Number或者String,再进行比较。
在JavaScript中,类型转换除了隐式转换外如上面,还有显式转换。显示转换通常都是利用String()、Number()、Boolean()和Object()来进行显式转换。由于undefined和null并没有toString()方法,所以利用Object()将他们转换为对象会报错。这里Boolean()将其他类型转换为布尔值,0、-0、NaN、undefined、''、0.0和'0'都会被转换成false,其他的会转换成true,如上面的一样。String()会将其他类型转换成字符串类型,转换的结果也和上面的一样。
而Number()由于内部有分parseInt()和parseFloat(),所以比较复杂。一般来说,通过Number()来将字符串转换为数值,如果是能转换的则数值为与字符串相当于,否则转换成NaN或者0,true会转换成1而false会转换成0,undefined会转换成NaN而null会转换成0。而在parseInt(string|number,radix)方法中,将字符串类型转换为数值型的话,首先radix参数表示的是转换的进制,如果忽略不写或者写为0的话则根据字符串的来决定输出的是多少进制,如:
var a = '11111111' //11111111
var b = 01234567 //342391
var c = '01234567' //1234567
var d = '0x111111' //1118481
var e = '12345678' //12345678
var f = '1aa1' //1
var g = 'true' //NaN
var h = true //NaN
var i = ' 1 ' //1
var j = ' 12 3 ' //12
var k = ' 1 2 3 ' //1
var l = 'aa1' //NaN
在这里,为什么b和c的结果差那么大。这是因为在字符串以“0”位开始时,旧的浏览器默认是使用八进制基数,在es3中则使用八进制或者十六进制基数,但是在es5中默认是十进制的基数。所以字符串变量c在新的浏览器中运用parseInt()方法转换过来的是1234567,而数值变量b由于是数值,所以前面的0在新的浏览器中会被识别,所以转换成八进制。除此以外,像变量f,在parseInt()方法中,如果字符串以合法开头截取则获取合法开头,即使后面还有合法字符串也不被截取。还有变量i、j和k,如果字符串是以空格为开头和结尾则空格被忽略,但是合法字符串的截取还是按照上面的规定。像变量g和h为什么输出的都是NaN呢,首先g是字符串,但是首字符并不是合法字符,所以不能转换成数字,只能返回NaN。而变量h是布尔值,连字符串值都不是,所以返回NaN。
在parseFloat(string)方法中,它的转换规则是这样的,从第一个合法字符开始,直到数字的末端结束,如果第一个并不是合法字符则返回NaN,如:
a = '123.45abc' //123.45
b = '123' //123
c = 'sdf' //NaN
d = ' 2e3a' //2000
值得注意的是,Number()比用parseInt()要严格的多,只要字符串内有一个字符无法转换成数值,那么Number()都会将整个字符串转换成NaN,而parseInt()会截取合法字符串部分。
参考文章:MDN isNaN()