“ 之前在看一篇文章时,忽然看到一篇有趣的文章,如何让x==1 && x==2 && x==3成立,话不多说,我们直接进入正文吧。”
1. ==和===区别
==操作符:表示宽松相等,它会在比较相等之前先将两边的值进行强制类型转换;
===操作符:表示严格相等,它不会执行类型转换,当两个值不是相同类型时,会返回false。
总结:==操作符只要求比较两个值是否相等,而===操作符不仅要求值相等而且还要要求类型相同。
2. ==具体解析
在大致介绍完==和===区别之后,我们再来回顾题目发现它使用到的是宽松相等操作符。先看看下面代码例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| // 类型相同时,只需要比较两边值是否相等即可 666 == 666 // true '666' == '666' // true false == false // true // 当然也有例外 NaN == NaN // false +0 == -0 // true {} == {} // false 对象是引用类型,两个对象分别指向不同引用,地址不相同
// 类型不相同时 // 1. 字符串和数字 // 首先会把字符串转化为数字,Number('666') == 666 '666' == 666 // true // 2. 布尔值和其他类型 // 如果布尔值和其他类型比较,会先把布尔值转成数字类型,true == 1 '666' == true // false 所以在写if判断时要注意这种错误写法 // 3. undefined和null // null和undefined互相相等 | 与其自身相等 null == undefined // true null == false // false undefined == 0 // false
|
上面代码介绍的都是基础类型的一些比较,还有一个特殊的比较:对象类型和其他类型比较;这样比较都会把对象类型转化成基本类型,转化成基本类型又分为2种:数字类型和字符串类型。其实在我之前的一篇文章中介绍了转换的规则,在这篇文章中我就不再讲述了,有兴趣的小伙伴可以看看我这篇文章。
对象转基本类型
3. 题目解析
对象转换成数字类型步骤:
如果是原始值(即一些代表原始数据类型的值,也叫基本数据类型),直接返回这个值;
如果是对象,调用valueOf(),如果得到结果是原始值,返回结果;
如果第二点不满足,则调用toString(),如果得到结果是原始值,返回结果;
如果都不满足,抛出错误。
对象转换成字符串类型步骤:
如果是原始值(即一些代表原始数据类型的值,也叫基本数据类型),直接返回这个值;
如果是对象,则调用toString(),如果得到结果是原始值,返回结果;
如果第二点不满足,调用valueOf(),如果得到结果是原始值,返回结果;
如果都不满足,抛出错误。
其实心细的你早已经发现了,转换类型是number或者string类型这两个的规则就是将第二点和第三点进行了一个翻转。
注意:
- 强转换为字符串类型时优先调用toString方法,强转换为数字类型优先调用valueOf;
- 在有运算操作符的情况下,toString和valueOf的优先级是:valueOf > toString;
- 进行对象转换时,toString和valueOf的优先级是:toString > valueOf,toString方法如果没有重写的话,会去看valueOf方法是否重写,如果两个方法都没有重写,那么就按照对象的toString方法输出。
在看了看了上面的总结之后,我们来分析一下如何写这个代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| /** * x == 1 && x == 2 && x == 3想要成立的话,x则应该是对象 * 当x为对象时,我们可以改写对象下的toString或者valueOf方法 * /
//改写toString方法 const x = { val: 0, toString: () => ++x.val, }
//改写valueOf方法 const x = { val: 0, valueOf: () => ++x.val, }
|
所以当执行x == 1 && x == 2 && x == 3时,等式比较会触发三次,使得val值递增三次,分别为1、2、3,可以看到如下图所示:

4. 延展
在介绍完上面的写法之后,我们再来看看对象转基本类型的实践与解析:
注意:
在实践之前需要记住的规则:只有当加法运算时,其中一方是字符串类型,就会把另一方也转化为字符串类型;而其他运算只要其中一方是数字,那么另一方就会转化为数字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| // 例一 1 + {} // '1[object Object]' /** * ({}).valueOf() ---> {} * ({}).toString() ---> '[object Object]' * 解析: *(1)由于上面说的规则,先看见数字1,此时右边的对象{}就会被转化成一个数字; *(2)由于转化类型是数字类型,它首先会调用valueOf()方法,此时还是返回对象{}, * 接着就会继续调用toString()方法,此时会被转化成'[object Object]'基本字符串类型; *(3)数字1再和字符串相加肯定就得到了上面我们所看到的结果'1[object Object]'。 */
// 例二 "1" + {} // "1[object Object]" /** * 解析: * 虽然和例一中得到的结果是一致的,但是其解析过程是不一样的,对于例二的解析步骤: *(1)先看见字符串1,此时右边的对象{}就会被转换成一个字符串; *(2)对象{}被转换成一个字符串,首先会调用toString()方法将 * 其转化成'[object Object]'基本数字类型; *(3)最后字符串"1"再和"[object Object]"字符串进行拼接得到结果"1[object Object]"。 */
// ----------------------难度升级----------------------- // 例三 [] + [] // "" /** * 解析: * 两边都是非数字或非字符串,记住一个原则:按照valueOf() ---> toString()顺序 * [].valueOf() ----> [] * [].toString() ----> "" *(1)按照说的规则,左右两边的数组[]都会去转化为数字类型, * 也就是valueOf--->toString顺序来执行; *(2)数组[]转化为数字类型,首先会调用valueOf()方法,此时会返回数组[], * 由于还是对象类型,它会接着去调用toString()方法,然后返回一个空的字符串""; *(3)所以[]+[]就相当于""+""然后得到""空字符。 */
// 例四 [] + {} // "[object Object]" /** * 解析: *(1)对于两边都是非数字或者非字符串,那么数组[]和对象{}都会被转换成数字类型; *(2)[]首先会去调用valueOf()方法转化为数组[],由于它是对象类型又会接着调用 * toString()方法,然后返回一个空的字符串""; *(3){}首先会去调用valueOf()方法转化为{},由于它是对象类型又会接着调用 * toString()方法,然后返回字符串"[object Object]"; *(4)""+"[object Object]"得到最终结果字符串"[object Object]"。 */
// --------------难度再次升级----------------------- // 例五 {} + [] // 0 /** * 解析: * {}可以看成是对象,但是大家有没有想过它也可以是代码块呢。如果{}放到前面的话, * 浏览器会把它解析成代码块,代码块意味着它是什么用都没有的。这个时候后面的"+" * 并不是二元运算符号,而是一个一元运算符。这个时候数组[]变为数字类型, * 经过valueOf()和toString()方法之后变为空字符串,然后+""一元符将空字符串转化为数字0。 */
// 例六 // 它是一个表达式 ({} + []) // "[object Object]" /** * 解析: * 例六就是对例五的一个扩展,其实再外层添加括号才能得到我们最开始想要的结果值。 * 这个时候在外层添加括号之后{}就不会被解析为代码块,会被解析为一个对象,至于过程中的 * 对象{}和数组[]都会按照valueOf()和toString()的顺序来解析,最终得到"[object Object]"返回值。 */
// 例七 {} + {} // NaN /** * 解析: * 首先需要声明:火狐浏览器解析出来时NaN,而Chrome谷歌浏览器解析出来是 * "[object Object][object Object]",此时需要看你的浏览器怎么解析了 * * 按照火狐浏览器来解析:第一个{}会被解析为代码块,然后将其忽略掉, * 然后加号会被解析为一元运算符,第二个{}对象会被解析为数字,首先执行valueOf()返回{}对象 * ,然后调用toString()返回字符串"[object Object]",由于+"[object Object]"会被转化 * 为数字,相当于隐私调用Number("[object Object]"),最后给你返回NaN的值 * * Chrome解析,和例五有区别,总结: * 如果第一个(前面)是{}时,后面加上其他的像数组、数字或字符串,这时候加号运算 * 会直接变为一元运算符,也就是强制转为数字的运算。 * ({}).valueOf() -> {} * ({}).toString() -> '[object Object]' */
|
通过上述代码,如果我们期待第一个{}不是一个代码块而是一个对象时,可以有两种做法:
(1)可以考虑强制把输入解析成一个表达式来修复,比如:{}+{}转换成({}+{});
(2)也可以考虑函数或方法的参数也会被解析成一个表达式,比如console.log({}+{})
,它得到的结果也是"[object Object][object Object]"
。
特别感谢下面文章的作者:
如何让 x == 1 && x == 2 && x == 3 等式成立
JS的{} + {}与{} + []的结果是什么?