JavaScript中的值类型转换分两种
类型转换(显式,发生在编译阶段)
var a = 42;
var b = String(a);
console.log(typeof b); //string
强制类型转换(隐式,发生在运行阶段)
var a = 42;
var b = a + "";
console.log(typeof b); //string
但是不管是哪一种,返回的都是标量基本类型值,不会返回函数和对象(因此我们之前说过的封装不是严格意义上的类型转换)
基本类型值的字符串化的规则为:
toString()可以被显式调用,也可以在字符串化的时候自动调用
(JSON.stringify(..)并不是强制类型转换,只是许多规则与ToString相同)
JSON.stringify(..)将JSON对象序列化为字符串时的规则
简单的转换
在转换对象的时候抽象操作ToPrimitive会首先通过内部操作DefaultValue
检查是否有valueOf方法,如果有并返回一个基本类型值,如果没有则对toString的值进行转化
如果valueOf和toString都不返回基本类型值,则会产生TypeError错误
JavaScript中的假值
遇到这些值时,强制类型转换会返回false,其他都为true
字符串和数字的转换通常通过String()和Number()进行(不带New,因此并不会创建封装对象)
var a = 42;
var b = String(a);
console.log(typeof b); //string
var a = "42";
var b = Number(a);
console.log(typeof b); //number
除了以上两种还有其他方式可以实现同样的效果
1. toString()
toString方法首先自动为42创建了一个封装对象然后再调用Object.prototype中的toString方法
var a = 42;
var b = a.toString();
console.log(typeof b); //string
2. +运算符
本例中的+a是+的一元形式(使用-也可以进行转化,但是会改变值),使用这种形式转化的数值进行运算的时候记得带空格(如+ +a)以区分++运算符
var a = "42";
var b = +a;
console.log(typeof b); //number
比较常见的用法有
var time = new Date();
console.log(time, typeof time); //Mon Sep 24 2018 15:42:41 GMT+0800 (中国标准时间) "object"
console.log(+time, typeof + time); //1537775001102 "number"
本例中将date对象强制转换为数字,返回一个Unix时间戳,值为从1970年一月一日00:00:00 UTC到当前时间的毫秒数
由于使用构造函数时,构造函数无参数是可以省略()的,因此可能会遇到+new Date这种用法
3.~运算符
~运算符跟以上两种运算符稍微有些不同
(注意 : ~在遇到NaN的时候会把NaN当做0来处理,比如~NaN=-1)
~作为位运算符首先有一个特点就是只适用于32位整数
因此~会先执行一个抽象操作ToInt32
而ToInt32又会先执行ToNumber进行一个强制类型转换然后再执行ToInt32
var a = "42";
var b = ~a;
console.log(typeof b, b); //number -43
~x大致等同于 -(x+1)
因此只有当x=-1的时候~x才会返回0(~是字位操作而不是数学运算,所以并不会返回-0)
-1是一个哨位值,被赋予了一个特殊含义,比如JavaScript中的indexOf(..)函数,返回-1表示不存在
但是直接使用>=0或者== -1进行判断的话存在一个抽象泄露的问题(暴露了底层的实现细节)
因此有个更好的方法是使用~运算符
~和indexOf(..)一起可以将结果强制类型转换为真/假值
当indexOf(..)返回-1的时候~运算符将-1转换为假值0,其他情况一律为真
比如 if(~a.indexOf("a")) 判断 a字符串中是否存在"a"这一个子字符串
~~进行字位截除
由于~字位运算符只适用于32位整数
因此可以是用来进行除去数字值的小数部分
第一个~执行ToInt32后进行字位反转,第二个~再次进行同样的步骤,因此结果其实是执行了ToInt32的原值
var a = "42.23333";
var b = ~~a;
console.log(b); //42
但是需要注意,这种方法只适用于32位数,且不完全等同于Math.floor(..)(向下取整)
var a = "-42.23333";
var b = ~~a;
console.log(b,Math.floor(a)); //-42 -43
事实上使用0|a(或运算符,对二进制进行或运算)也能达到一样的效果,而且看起来还更简便一些,因为|运算符的空操作(0|x)只进行了ToInt32操作
var a = "-42.23333";
var b = a | 0;
console.log(b); //-42
但是如果考虑到优先级的问题的话,我还是更倾向于使用~~运算符
~~20 / 10 //2
20 | 0 / 10 //20
(20 | 0) / 10 //2
Number("42"),//42
Number("42px"),//NaN
parseInt("42"),//42
parseInt("42px"),//42
解析字符串允许字符串中含有非数字字符,解析按照左到右,遇到非数字字符停止
转换不允许出现非数字字符,否则失败并返回NaN
parseInt()针对的是字符串值,如果传入非字符串,那么会被强制转换为字符串
在ES5之前的parseInt()有一个大坑
如果没有第二个参数来指定转换的基数,那么会根据字符串中的第一个字符来决定
如果第一个字符是x或者X则转换为16进制
如果第一个字符是0,则转换为八进制
将第二个参数设定为10则可以避免该问题
ES5以后默认转换为10进制,除非另外指定
解析非字符串
parseInt(1 / 0, 19) //18
看到这个例子是不是觉得有点无法理解?
解析非字符串的时候,首先会把参数强制转换为字符串再解析
1/0的结果是Infinity,而JavaScript中所有的值都有一个默认的字符串形式,所以可以直接转为"Infinity"
再回到基数19,代表有效数字范围为0-9和a-i
这个时候解析到"I"的时候是没问题的,但是接下来的"n"不在有效数字范围内,就像上面遇到了"42px"中的"p"一样
解析停止,返回的是"I",也就是18
此外还有一些看起来奇怪但是没有问题的例子
console.log(
parseInt(0.000008), //0,来自于0.000008中的0
parseInt(0.0000008), //8,来自于8e-7中的8
parseInt(false, 16), //250,来自于false中的fa
parseInt(parseInt, 16), //15,来自function..中的f
parseInt("0x10"), //16,来自于0x10
parseInt("103", 2), //2,来自于103中的10
)
和上面的String()和Number()一样Boolean(..)(不带new)也是显式的ToBoolean强制类型转换,但是这种方法并不常用
与++转换number类型类似,!!同样也可以用来转换boolean类型
console.log(
!!"0", //true
!![], //true
!!{}, //true
!!"", //false
!!0, //false
!!null, //false
!!undefined //false
)
在if(..)这样的布尔值上下文中,如果没有使用Boolean(..)和!!,就会自动进行ToBoolean转换