前言
JS 内最为诟病的便是其隐式类型转换问题,对于初学者来说,其复杂的规则以及诡异的结果很容易一头雾水。这难道表明 JS 不是很好吗?其实不然,隐式的转换带给我们的是代码的简介度,在合适的时机使用,同样会带来优质的 coding 体验。本文就来梳理一下 JS 类型转化规则,方便大家理解。
本篇参考文章 《你所不知道的 JavaScript 中篇》 第四章
总体转换规则
文档内介绍的比较清除,此处为了更好的展示效果,仅引用一下截取自 Alex Dorey 的 GithubIO 的一张图:
仔细理解一下,对于 JS 中很多相等比较的奇妙结果便都能解释了。比如:
1 | '0' == false; // true |
当 string 与 boolean 类型比较时,会优先将 boolean 转化为 number,因此我们得到中间步骤为:
1 | // Step 1 |
然后 string 比较 number 时,此时会优先将 string 转化为 number,因此我们得到
1 | // Step 2 |
最终的结果显而易见,那就是 true 了。
极端情况
当然我们不排除有些难以理解的极端情况,以下列举一个 Case:
1 | [] == ![]; // true |
分析一下,由于 !
的存在,我们优先进行显示转换,于是有:
1 | // Step 1 |
然后对象与 boolean 类型比较时,我们需要调用对象的 [[DefaultValue]] 方法(toString/valueOf),因此我们得到
1 | // Step 4 |
这一步又回到了 string 与 boolean 的比较了,很明显最终的结果为 true。
此外,还有部分比较难以理解的,这里就稍微列举一下:
1 | // 数组单个值的解析 |
1 | // 对象的解析 |
逻辑运算符
隐式类型转换相关的还有逻辑运算符,但是 JS 中的逻辑运算符又和其它强类型语言(如 C)的逻辑运算符不同,其返回值并非 boolean 类型。我们看个例子:
1 | const a = 4; |
可见,|| 和 && 首先会对第一个操作数(a 和 c)执行条件判断,如果其不是布尔值就进行 toBoolean
强制类型转换,然后再执行条件判断。
其规律为:
对于 || 来说,如果条件判断的结果为 true 就返回第一个操作数(a 和 c)的值,否则返回第二个操作数(b)的值。
对于 && 来说,如果条件判断的结果为 true 就返回第二个操作数(b)的值,否则返回第一个操作数(a 和 c)的值。
换个角度来看,它们很像三元运算符,但是它不会有副作用即:其不会执行两次。如 a ? a : c,如果 a 为比较复杂的运算,那么 a 会执行两次,但是 a || c,其 a 只会执行一次。
此“逻辑运算符”在 JavaScript 中可能更为贴切的叫法为“操作数选择器”吧,在大多数的代码压缩后的内容中我们常常能见到其影子,工作中不妨也可以一试 ☆*:.。. o(≧▽≦)o .。.:*☆
&& 和 || 运算符的返回值并不一定是布尔类型,而是两个操作数其中一个的值。 —— ES5 规范 11.11 节
代码块
我们知道,在 JS 中 { /_ … _/ } 表明这是一个对象,但是我们常常忘了它还能表示为代码块,比如 while / if 后面的大括号。然而这和 JS 类型转换有什么关系呢,我们看看下例:
1 | [] + {}; // [object Object] |
那么问题来了,仅仅调换了一个位置,为什么结果会相差这么多?
分析一下,在第一个例子中,{} 是在 + 运算符表达式中,词法分析将 {} 解析为第二个参数,因此经过隐式转换,{} 会被转换成 ‘[object Object]’,空数组就不用多解释了,所对应的结果也就如上了。
然而第二个却有些不同。我们知道 {} 作为空代码块是不执行任何操作的,且不用带 ; 结尾,所以在此处是不存在语法问题的。因此自左向右来看,词法分析将此分解为 {} 与 +[] 两部分,前者作为空代码块执行,无任何副作用,后者强制转换结果就是 0 了。