js类型转换知识总结
显示强制类型转换
显式强制类型转换 是指那些显而易见的类型转换
字符串和数字之间的显示转换
通过String(..) 和 Number(..)两种方法来实现
1 | var a = 42; |
除了 String(..) 和 Number(..) 以外,还可以通过toString(..)和三元符号+来实现显示转换
1 | var a = 42; |
日期显式转换为数字
1 | var d = new Date( "Wed Nov 16 2022 21:17:41 GMT+0800" ); |
获取当前时间戳
1 | var timestamp = +new Date(); |
~ 运算符
~ 运算符,(即字位操作“非”),~x 大致等同于 -(x+1)
1 | ~42; // -(42+1) ==> -43 |
显式解析数字字符串
1 | var a = "42"; |
解析允许 字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许 出现非数字字符,否则会失败并返回 NaN 。
解析非字符串
1 | 1/0; // Infinity |
parseInt(1/0, 19) 实际上是 parseInt(“Infinity”, 19) 。第一个字符是 “I” ,以 19 为基数时值为 18 。第二个字符 “n” 不是一个有效的数字字符,解析到此为止,和 “42px” 中的 “p” 一样。
1 | parseInt( new String( "42") ); // 42 |
先将参数强制类型转换为字符串再进行解析,这样做没有任何问题。因为传递错误的参数而得到错误的结果,并不能归咎于函数本身。
还有一些奇怪的现象
1 | parseInt( 0.000008 ); // 0 ("0" 来自于 "0.000008") |
显式转换为布尔值
Boolean(..) (不带 new )是显式的 ToBoolean 强制类型转换
1 | var a = "0"; |
和
+类似,一元运算符!显式地将值强制类型转换为布尔值。但是它同时还将真值反转为假值(或者将假值反转为真值)。
在
if(..)..这样的布尔值上下文中,如果没有使用Boolean(..)和!!,就会自动隐式地进行 ToBoolean 转换
ToBoolean 的另外一个用处,是在 JSON 序列化过程中将值强制类型转换为 true 或 false
1 | var a = [ |
隐式强制类型转换
隐式强制类型转换 指的是那些隐蔽的强制类型转换,副作用也不是很明显
字符串和数字之间的隐式强制类型转换
+ 运算符即能用于数字加法,也能用于字符串拼接
1 | var a = "42"; |
如果
+的其中一个操作数是字符串(或者通过以上步骤可以得到字符串),则执行字符串拼接;否则执行数字加法
1 | var a = { |
a + “” (隐式)和前面的 String(a) (显式)之间有一个细微的差别需要注意。根据 ToPrimitive 抽象操作规则,a + “” 会对 a 调用 valueOf() 方法,然后通过 ToString 抽象操作将返回值转换为字符串。而 String(a) 则是直接调用 ToString() 。
1 | var a = "3.14"; |
-是数字减法运算符,因此 a - 0 会将 a 强制类型转换为数字
布尔值到数字的隐式强制类型转换
应用场景
1 | function onlyOne(a,b,c) { |
这里代码阅读性很差,如果差数多起来就更加不好维护,这里改用另外一种方法
1 | function onlyOne() { |
也可以修改成如下,代码阅读性更好
1 | function onlyOne() { |
隐式强制类型转换为布尔值
下面的情况会发生布尔值隐式强制类型转换
(1) if (..) 语句中的条件判断表达式。
(2) for ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)。
(3) while (..) 和 do..while(..) 循环中的条件判断表达式。
(4) ? : 中的条件判断表达式。
(5) 逻辑运算符 || (逻辑或)和 && (逻辑与)左边的操作数(作为条件判断表达式)。
1 | if (a || b) .... if (a && b) .... |
|| 和 &&
|| 和 &&叫做“逻辑运算符”,因为这不太准确。称它们为“选择器运算符”
1 | var a = 42; |
||和&&首先会对第一个操作数 (a 和 c )执行条件判断,如果其不是布尔值(如上例)就先进行 ToBoolean 强制类型转换,然后再执行条件判断。对于
||来说,如果条件判断结果为true就返回第一个操作数(a 和 c )的值,如果为false就返回第二个操作数(b )的值。
&&则相反,如果条件判断结果为true就返回第二个操作数(b )的值,如果为false就返回第一个操作数(a 和 c )的值。
有一种用法对开发人员不常见,然而 JavaScript 代码压缩工具常用。就是如果第一个操作数为真值,则 && 运算符“选择”第二个操作数作为返回值,这也叫作“守护运算符”
1 | function foo() { |
foo() 只有在条件判断 a 通过时才会被调用。如果条件判断未通过,a && foo() 就会悄然终止(也叫作“短路”,short circuiting),foo() 不会被调用。
|| 和 && 在if语句
1 | var a = 42; |
这里 a && (b || c) 的结果实际上是 “foo” 而非 true ,然后再由 if 将 foo 强制类型转换为布尔值,所以最后结果为 true 。
符号的强制类型转换
1 | var s1 = Symbol( "cool" ); |
符号不能够被强制类型转换为数字(显式和隐式都会产生错误),但可以被强制类型转换为布尔值(显式和隐式结果都是 true )。
宽松相等和严格相等
宽松相等(loose equals)== 和严格相等(strict equals)=== 都用来判断两个值是否“相等”,但是它们之间有一个很重要的区别,特别是在判断条件上。
==允许在相等比较中进行强制类型转换,而===不允许。
1 | var a = 1; |
相等比较操作的性能
==和===仅仅是微秒级(百万分之一秒)的差别而已
抽象相等
1 | NaN == NaN; // false |
字符串和数字之间的相等比较
1 | var a = 42; |
因为没有强制类型转换,所以 a === b 为 false ,42 和 “42” 不相等。
而 a == b 是宽松相等,即如果两个值的类型不同,则对其中之一或两者都进行强制类型转换。
具体怎么转换?是 a 从 42 转换为字符串,还是 b 从 “42” 转换为数字?
(1) 如果 Type(a) 是数字,Type(b) 是字符串,则返回 a == ToNumber(b) 的结果。
(2) 如果 Type(a) 是字符串,Type(b) 是数字,则返回 ToNumber(a) == b 的结果。
其他类型和布尔类型之间的相等比较
1 | var a = "42"; |
(1) 如果 Type(a) 是布尔类型,则返回 ToNumber(a) == b 的结果;
(2) 如果 Type(b) 是布尔类型,则返回 a == ToNumber(b) 的结果。
Type(x) 是布尔值,所以 ToNumber(x) 将 true 强制类型转换为 1 ,变成 1 == “42” ,二者的类型仍然不同,”42” 根据规则被强制类型转换为 42 ,最后变成 1 == 42 ,结果为 false 。
Type(y) 是布尔值,所以 ToNumber(y) 将 false 强制类型转换为 0 ,然后 “42” == 0 再变成 42 == 0 ,结果为 false 。
代码中不要使用 == true 和 == false
null 和 undefined 之间的相等比较
1 | var a = null; |
(1) 如果 x 为 null ,y 为 undefined ,则结果为 true 。
(2) 如果 x 为 undefined ,y 为 null ,则结果为 true 。
在 == 中 null 和 undefined 相等(它们也与其自身相等),除此之外其他值都不存在这种情况。
对象和非对象之间的相等比较
1 | var a = 42; |
(1) 如果 Type(a) 是字符串或数字,Type(y) 是对象,则返回 a == ToPrimitive(b) 的结果;
(2) 如果 Type(a) 是对象,Type(b) 是字符串或数字,则返回 ToPromitive(a) == b 的结果。
| input Type | Result |
|---|---|
| Undefined input | argument |
| Null input | argument |
| Boolean input | argument |
| Number input | argument |
| String input | argument |
| Object | 忽略 第二个参数 hint PreferredType 直接调用内置方法 [[DefaultValue]] |
1 | var a = "abc"; |
a == b 结果为 true ,因为 b 通过 ToPromitive 进行强制类型转换(也称为“拆封”,英文为 unboxed 或者 unwrapped),并返回标量基本类型值 “abc” ,与 a 相等。
也有例外的情况
1 | var a = null; |
因为没有对应的封装对象,所以 null 和 undefined 不能够被封装(boxed),Object(null) 和 Object() 均返回一个常规对象。
NaN 能够被封装为数字封装对象,但拆封之后 NaN == NaN 返回 false ,因为 NaN 不等于 NaN
比较少见的情况
返回其他数字
1 | Number.prototype.valueOf = function() { |
如果让 a.valueOf() 每次调用都产生副作用,比如第一次返回 2 ,第二次返回 3 ,就会出现这样的情况
1 | var i = 2; |
假值的相等比较
1 | "0" == null; // false |
然而有 7 种我们注释了“晕!”,因为它们属于假阳(false positive)的情况,里面坑很多。
极端情况
1 | [] == ![] // true |
根据 ToBoolean 规则,它会进行布尔值的显式强制类型转换(同时反转奇偶校验位)。所以 [] == ![] 变成了 [] == false 。前面我们讲过 false == []
1 | 2 == [2]; // true |
== 右边的值 [2] 和 [null] 会进行 ToPrimitive 强制类型转换,以便能够和左边的基本类型值(2 和 “” )进行比较。因为数组的 valueOf() 返回数组本身,所以强制类型转换过程中数组会进行字符串化。
第一行中的 [2] 会转换为 “2” ,然后通过 ToNumber 转换为 2 。第二行中的 [null] 会直接转换为 “” 。
所以最后的结果就是 2 == 2 和 “” == “”
1 | 0 == "\n"; // true |
“” 、”\n” (或者 “ “ 等其他空格组合)等空字符串被 ToNumber 强制类型转换为 0
完整性检查
1 | "0" == false; // true -- 晕!避免使用!!!!!! |
抽象关系比较
比较双方首先调用 ToPrimitive ,如果结果出现非字符串,就根据 ToNumber 规则将双方强制类型转换为数字来进行比较
1 | var a = [ 42 ]; |
如果比较双方都是字符串,则按字母顺序来进行比较
1 | var a = [ "42" ]; |
a 和 b 并没有被转换为数字,因为 ToPrimitive 返回的是字符串,所以这里比较的是 “42” 和 “043” 两个字符串,它们分别以 “4” 和 “0” 开头。因为 “0” 在字母顺序上小于 “4” ,所以最后结果为 false
1 | var a = { b: 42 }; |
a 是 [object Object] ,b 也是 [object Object] ,按照字母顺序 a < b 并不成立