混乱邪恶:JavaScript中的相等操作符(==)
昨天睡觉前刷到一道面试题,这个问题萦绕在我的脑海里,使得我失眠到凌晨3点。题干很简单:
[] == ![]的结果是什么?
所以我们今天来介绍一下问题的主角——相等操作符==,看完今天的文章,也许技术上没有什么提升,因为这玩意坑太多在开发中是极力避免使用的。但是这是一个很好的JavaScript式的问题。即如何在混乱中走出一条秩序的道路。
现在请回答以下表达式的返回值,以及if语句会不会执行。
1 | 0 == false |
答案是:
1 | 0 == false // true |
明明'0' == false成立,为什么'0'在if中被认为是true呢?(现在不要深究这个问题,否则大脑会stack overflow的,相信我)这个结果混乱的相等操作符==是无数BUG的根源。有人总结过各种类型的值使用==比较后的结果,有兴趣的话可以看看JS Comparision Table。Reddit上一位网友对此的评论,我认为很精髓:

¶万恶之源——隐式类型转换
==之所以变得那么不讲道理,是因为在使用==时,对两边表达式进行隐式类型转换。这里,我推荐一篇文章“从[]==![]为true来剖析JavaScript各种蛋疼的类型转换”。这篇文章把整个==涉及的表达式值的转换过程讲的非常清楚。字比较多,大家慢慢看哈wwww。
好,看完回来,相信大家对隐式转换的过程有一定的了解了。但是这篇文章对toPrimitive介绍的不够清晰,这里我再推荐一篇文章Object to primitive conversion。
让我们总结一下从上面学到的一些东西:
在相等操作符中:
undefine == null,undefine和null与其他任何类型比较均为false;- 若两边类型相同,按照各自类型的抽象相等规则比较: 2.1
number比较大小; 2.2string只有在长度和对应位置字符都相等才相等; 2.3boolean,不用说了吧… 2.4object,比较类在内存中的地址(即两个类指向的是同一个类);- 基本类型(
boolean,string)转化为number后相互比较;Object使用ToPrmitive算法转化为基本类型之后按2比较。
ToPrimitive算法: 在相等操作符中,内置的
Object(除了Date)均以default为hint转换为基本类型:
- 如果有
.valueOf(),尝试使用.valueOf()返回基本类型;- 如果没有
.valueOf(),或者上一步尝试失败,但是有.toString(),尝试使用.toString()返回基本类型;- 如果都没有,或者上一步尝试失败,报错
TypeError;
Date以string为hint转化为基本类型:
- 如果有
.toString(),尝试使用.toString()返回基本类型;- 如果没有
.toString(),或者上一步尝试失败,但是有.valueOf(),尝试使用.valueOf()返回基本类型;- 如果都没有,或者上一步尝试失败,报错
TypeError
如果对Date的转换规则有疑虑的话,可以运行下面的代码验证:
1 | const date = new Date() |
¶基本Object的类型转换
下面看一些常用的基本类型的.valueOf()以及toString()是什么,列表总结如下:
| 类型 | 具体值 | .valueOf() |
.toString() |
|---|---|---|---|
Object |
{a: 1, b: 2} |
{a: 1, b: 2} |
"[object Object]" |
Array |
[1, 2, 3] | [1, 2, 3] |
"1, 2, 3" |
Date |
new Date('1970/1/1 00:00:00 GMT') |
0 |
"Thu Jan 01 1970 08:00:00 GMT+0800 (中国标准时间)" |
可以看到Object与Array的ToPrimitive的首选方法.valueOf()返回的均不是基本类型之一,因此会使用toString()。。。Oh my god。。
¶那么我们开篇问题的答案是
是true。先写出整个表达式[] == ![],右边![]是false(对于非操作符,是这么操作的!toBoolean(GetValue(expr))),而对于所有Object,toBoolean的结果都是true,所以右边整个式子是false,然后这个布尔值转化为数字是0。接下来我们来看左边,首先[]用toPrimitive转化为基本类型是"",然后这个字符串转化为数字,结果也是0。最后0 == 0,当然是true啦。
¶总结
我一直认为相等操作符是JavaScript的设计失误之一。要弄清楚一个相等操作符的结果,与做一道数学证明题相似,从题目到中间结果到中间结果…到结果。所以,==仅适用于题目,而对于开发项目而言可能是万恶之源。好在JavaScript提供了正常一些的严格相等操作符===。所以总结就是:
Always use 3 equals unless you have a good reason to use 2.