强制类型转换
JavaScript的值具有类型,所以JavaScript也有强制类型类型转换,对于动态语言,强制类型转换总是发生在运行时。强制类型转换分为隐式和显式,有一种区分方法是:如果我一眼就能看懂了这是强制类型转换,那么他就是显式的,否则,他就是隐式的。比如下面:
var a = 1
b = "" + a
c = String(a)
一、toString方法
JavaScript的几乎所有数据类型的实例都具有toString方法,这可以理解成转换成字符串的类型转换。
//1.Number
1..toString() //=> "1"
//2.Boolean
true.toString() //=> "true"
//3.Object
({}).toString() //=>"[object Object]"
//4.Function
(function(){}).toString() //=> "function (){}"
//5.Symbol
Symbol("1").toString() //=> "Symbol(1)"
因为 null和undefined没有toString()方法,String类型toString还是本身,所以以上的例子基本包含了JavaScript所有数据类型(介绍typeof那里的全部)的toString方法。当然,这并不是全部,因为还有一些引用类型的toString方法结果并不是”[object Object]”。
//1.Array
([1,2]).toString() //"1,2"
//2.Date
new Date().toString() //=>"Sun Dec 09 2018 21:00:29 GMT+0800 (中国标准时间)"
//3.Error
new Error("error").toString() //=>"Error: error"
//4.RegExp
new RegExp(/regexp/).toString() //=>"/regexp/"
再加上上面的例子,基本就包含了所有的类型的toString方法,class定义的默认是继承了Object,所以为返回”[object Object]”。
这里补充一点,我之前说过var a = 1和var a = new Number(1)这2种给变量赋值是有区别的,这里1..toString()仍然使用的是Number的toString方法
Number.prototype.toString = function(){return "toString已被修改!"}
1..toString() //=>"toString已被修改!"
toString还有一个特点就是它支持进制,toString接收一个数值参数,如果处理的对象是Number类型,那么会先将其转换成对应的进制,然后再输出字符串结果:
(11).toString(8) //"13"
new Number(11).toString(8) //"13"
("11").toString(8) //"11"
最后是一个总结加补充:
1.一般情况下其他类型转换成字符串类型都有自带的toString方法,转换出来是什么结果前面已有例子,我们也可以重写这些toString函数达到我们想要的效果,重写:Number.prototype.toString = function(){return "toString已被修改!"},一定要修改原型链上的toString方法。
2.null和undefined没有toString方法,但是我们可以这样String(null)和String(undefined),这相当于调用的String函数,而不是toString方法
二、其他类型转换成Boolean
其他类型转换成布尔值我们没有直接的toBoolean方法,但是在if(a) 中,a会被强制转换成Boolean值,类似的也有Boolean方法(不常用,一般布尔值是在if条件里面的,默认就会被转换),比如Boolean(a)。转换成布尔值的规则相对比较简单,只需要记住哪些是false就行了,因为其他都是true。并且更有趣的是引用类型都是true,只有基本类型才有可能是false。
- +0,0,-0,NaN
- null //注意”null”是true,但是null是false
- undefined //同上
- "" //空字符串
- false //这个本身就是布尔值false
这里有一点需要注意的是下面的情况:
var a = new Boolean(false)
if(a){
console.log("想不到吧,会进入循环!")
}
console.log(a.toString()) //=>false
上面的例子a会进入循环,因为这里a是一个引用类型的值,而不是真正的布尔值。所以这也是不要使用封装对象给变量赋值的原因,它总有一些意想不到的情况发生。
最后再补充一个很简单的强制转换写法 !!a,这样就能将其他类型转换成布尔值。
三、其他类型转换成Number
我想转换成Number用到最多的应该是Number函数(因为没有toNumber方法-。-),Number仅接收一个参数,如果可以转换成合法数字就返回转换的那个数字,如果不是合法数字就返回NaN,他一般都是处理字符串转换成数字。(数字是指int和float)
Number(1.1) // =>1.1
Number("1.1") // =>1.1
Number("1.a") // =>NaN
Number(undefined) //=>NaN
Number(Symbol("")) //报错
Number(true) //=>1
Number(false) //=>0
Number(new Boolean(true)) //=>1,最好还是不要让这种情况的转换发生
Number(new Boolean(false)) //=>0,最好还是不要让这种情况的转换发生
Number({}) //=>NaN 引用类型基本都是转换成NaN,但是有2个例外
Number(new Date) //输出的当前时间的时间戳,这个是例外
Number([]) // =>0
Number([1]) //=>1
Number([1,2]) //NaN,对于数组,如果数组的长度为1,那么取第一个元素,将其转换成Number类型,但是还是需要注意一些例外
Number([undefined]) //=>0
Number([true]) //=>NaN
Number([false]) //=>NaN
//下面是一些转换成0的情况
Number() //=>0
Number(null)/=>0
Number("") //=>0
Number([]) // =>0
//Number无法指定进制,但是它有对进制的支持
Number("0x11") //17
Number("0o11") //9
Number("0b11") //3
Number的原理:
1.首先判断参数是否是基本类型(封装对象赋值的变量当成引用类型),如果是基本类型,那么按照对应的处理。
2.如果不是基本类型,那么调用其valueOf方法,看valueOf返回的值是否是基本类型。如果valueOf返回的值是基本类型,按基本类型处理。
3.否则,调用toString方法,判断toString方法返回的值是否是基本类型
Number(new Boolean(true)) //=>1
//valueOf是基本类型的情况
Boolean.prototype.valueOf = function(){return "233"}
Number(new Boolean(true)) //=>233
Boolean.prototype.valueOf = function(){return "[]"}
Number(new Boolean(true)) //=>NaN
//valueOf不是基本类型的情况
Boolean.prototype.valueOf = function(){return []}
Number(new Boolean(true)) //=>NaN,因为toString()的结果是 "true" 而Number("true")是NaN
Boolean.prototype.toString = function(){return "233"}
Number(new Boolean(true)) //=>233
Boolean.prototype.toString = function(){return []}
Number(new Boolean(true)) //=>报错
//下面是一个数组的例子
Array.prototype.valueOf = function(){return true}
Number([]) //=> 1
//valueOf不是基本类型的情况
Array.prototype.valueOf = function(){return []}
Number([2]) //=>2,因为[2].toString()的结果就是"2"
Array.prototype.toString = function(){return "233"}
Number([2]) //=>233
Array.prototype.toString = function(){return []}
Number([2]) //=>报错
对于Number函数我们可以做如下总结:
- 引用类型基本全都是NaN,但是Date是时间戳,数组会取第一个元素进行判断。
- 基本类型里面除了undefined和不合法的数字字符串是NaN以外其他都可以转换成正常数字。
- 前面提到的那些,合法数字字符串转换成对应的数字,true转换成数字是1,其他(空串,null,false,不传参数)都是0
除了Number当然还有其他函数也是将其它类型转换成数字的:parseInt,parseFloat。
parseInt(转换成整数)
与其说parseInt是转换,还不如说它是解析字符串成整数。它的第二个参数是可选参数,可以选择进制,默认是10进制(ES5之前会根据第一位字符进行判断,比如parseInt("08") 输出0,因为以0开头默认按8进制解析,这个真实情况我没验证,ES5默认是10进制)。它处理的有效位数是截至第一个非法字符,这么描述我自己也不懂,还是看几个例子:
parseInt("16px") //=>16,因为p是非法字符,所以转换的有效字符串是"16"
parseInt("px16") //=>NaN,因为第一个字符就是非法的
//下面的几种可以和Number对比一下
parseInt("") //=>NaN
parseInt(true) //=>NaN
parseInt(false) //=>NaN
parseInt(null) //=>NaN
parseInt(undefined) //=>NaN
//下面是几种引用类型
parseInt(new Date) //=>NaN
parseInt({}) //=>NaN
parseInt([]) //=>NaN
parseInt([1]) //=>1
parseInt([1,2]) //=>1,这里的区别是当数组具有多个元素时,多余的会被忽略,没有元素返回NaN
parseInt([2,1]) //=>2
//下面观察解析其他进制的情况
parseInt("0xFF") //=>255
parseInt("0xFF",16) //=>255
parseInt("011") //=>11,这个并没有当成8进制
parseInt("0o11") //=>0,这个也没有当成8进制(console.log(0o11) => 9)
parseInt("0o11",8) //=>0,还是0
parseInt("11",8) //=>9
parseInt("0b11") //0
我们还需要深究一下parseInt的原理,不然下面的现象解释不了。
parseInt(1/0,19) //=>18因为 1/0 = Infinity , 19进制 I 是有效字符 ,parseInt("I",19)结果18
parseInt(0.000008) //0 因为这相当于"0.000008",小数点后不算有效字符
parseInt(0.0000008) //8 因为这等价于 "8e-7" ,e后不算有效字符
parseInt(parseInt,16) //15 因为function(){...}第一位f是有效字符
原理:除了直接的字符串和直接的整数,parseInt会将参数转换成字符串类型,转换方法是调用对应变量的toString方法,然后再进行解析。
Function.prototype.toString = function(){return "666"}
parseInt(parseInt) //=>666
Number.prototype.toString = function(){return "666"}
parseInt(1) //=>1
parseInt(new Number(1)) //=>666,因为a = new Number(1),这里的a是引用类型
String.prototype.toString = function(){return "666"}
parseInt("1") //=>1
parseInt(new String(1)) //=>666,因为a = new String(1),这里的a是引用类型
parseFloat原理和parseInt差不多,只是除了能解析整数以外还能解析浮点数。最后还要补充一个Number的快捷的方式代码如下:
+"12.34" //=>12.34
1+ +"12.34" //13.34,注意中间的空格
它的解析方式和Nunber差不多。