今天我们通过一道具体的题目来探析一些隐式类型转换。
一、题干
如何让条件成立,成功打印‘Hello World’?
if(b == 1 && b == 2 && b == 3) { console.log('Hello World!'); }
二、思路
刚开始看到这道题的时候因为看到了==内心马上想到了隐式类型转换,但是如果b是字符串的话肯定无法满足b == 1 && b == 2 && b == 3, 这个时候去mdn查看一下==隐式类型转换的规则:
相等运算符(==和!=)使用抽象相等比较算法比较两个操作数。可以大致概括如下:
- 如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回true。
- 如果一个操作数是null,另一个操作数是undefined,则返回true。
- 如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型:
- 当数字与字符串进行比较时,会尝试将字符串转换为数字值。
- 如果操作数之一是Boolean,则将布尔操作数转换为1或0。
- 如果是true,则转换为1。
- 如果是 false,则转换为0。
- 如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的valueOf()和toString()方法将对象转换为原始值。
- 如果操作数具有相同的类型,则将它们进行如下比较:
- String:true仅当两个操作数具有相同顺序的相同字符时才返回。
- Number:true仅当两个操作数具有相同的值时才返回。+0并被-0视为相同的值。如果任一操作数为>NaN,则返回false。
- Boolean:true仅当操作数为两个true或两个false时才返回true。 此运算符与严格等于(===)运算符之间最显着的区别在于,严格等于运算符不尝试类型转换。相反,严格相等运算符始终将不同类型的操作数视为不同。
本题与b相比较的是1 2 3,这几个数字,如果b也是数字那么条件(b == 1 && b == 2 && b == 3)不可能成立(假设b为1,则b不可能为2),所以==的两个操作数一定是不同类型的:
相等运算符(==和!=)使用抽象相等比较算法比较两个操作数。可以大致概括如下:
如果两个操作数都是对象,则仅当两个操作数都引用同一个对象时才返回true。如果一个操作数是null,另一个操作数是undefined,则返回true。- 如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型:
- 当数字与字符串进行比较时,会尝试将字符串转换为数字值。
- 如果操作数之一是Boolean,则将布尔操作数转换为1或0。
- 如果是true,则转换为1。
- 如果是 false,则转换为0。
- 如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的valueOf()和toString()方法将对象转换为原始值。
如果操作数具有相同的类型,则将它们进行如下比较:
String:true仅当两个操作数具有相同顺序的相同字符时才返回。Number:true仅当两个操作数具有相同的值时才返回。+0并被-0视为相同的值。如果任一操作数为>NaN,则返回false。Boolean:true仅当操作数为两个true或两个false时才返回true。此运算符与严格等于(===)运算符之间最显着的区别在于,严格等于运算符不尝试类型转换。相反,严格相等运算符始终将不同类型的操作数视为不同。
如果b为字符串或者Boolean值,b被类型转换的之后会变成一个确定的值,那么条件(b == 1 && b == 2 && b == 3)也不可能成立。所以只剩下最后一种可能,那就是b为对象,经过valueOf或者toString方法返回的值可以使得条件(b == 1 && b == 2 && b == 3)成立。
- 如果两个操作数是不同类型的,就会尝试在比较之前将它们转换为相同类型:
当数字与字符串进行比较时,会尝试将字符串转换为数字值。如果操作数之一是Boolean,则将布尔操作数转换为1或0。
如果是true,则转换为1。如果是 false,则转换为0。- 如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的valueOf()和toString()方法将对象转换为原始值。
知道了这些之后就可以构造b了。
const b = { i: 1, toString: function() { return b.i++ } }
这样每次调用b == 1/ b == 2 / b == 3的时候都会执行toString方法,从而使得条件成立。
可以在控制台打印下试试:
三、补充
还记得mdn上的这句话吗?
- 如果操作数之一是对象,另一个是数字或字符串,会尝试使用对象的valueOf()和toString()方法将对象转换为原始值。
这句话说明构造b的时候用valueOf也是可以的。但是如果toString和valueOf同时被改写了,最终会执行哪个呢?
可以发现,程序只执行了valueOf方法。这是为什么呢?原因是这样的:
有时我们需要将非数字值当作数字来使用,比如数学运算。为此ES5规范在9.3节定义了抽象操作ToNumber。其中true转换为1, false转换为0。undefined转换为NaN, null转换为0。ToNumber对字符串的处理基本遵循数字常量的相关规则/语法(参见第3章)。处理失败时返回NaN(处理数字常量失败时会产生语法错误)。不同之处是ToNumber对以0开头的十六进制数并不按十六进制处理(而是按十进制,参见第2章)。数字常量的语法规则与ToNumber处理字符串所遵循的规则之间差别不大,这里不做进一步介绍,可参考ES5规范的9.3.1节。对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。为了将值转换为相应的基本类型值,抽象操作ToPrimitive(参见ES5规范9.1节)会首先(通过内部操作DefaultValue,参见ES5规范8.12.8节)检查该值是否有valueOf()方法。如果有并且返回基本类型值,就使用该值进行强制类型转换。如果没有就使用toString()的返回值(如果存在)来进行强制类型转换。如果valueOf()和toString()均不返回基本类型值,会产生TypeError错误。
四、参考文献
-
《你不知道的JavaScript(中卷)》4.2.2
本题题源来自朋友派大欣,特此感谢!
第一次写技术博文,欢迎大佬批评指正 ~~~ 如在工作中有这道题的应用场景欢迎分享,不胜感激!