JavaScript中的比较运算符
JavaScript中的比较运算符粗略的可以分为两种:
- 相等运算符(==、===、!==)这些
- 关系运算符(>、<、>=)
在平时开发中,基本不会太关注这两者的差异,我们几乎总是可以获取到我们想要的结果-。-
前几天在群里一个小伙伴问了个问题:
console.log(null > 0) // => false
console.log(null < 0) // => false
console.log(null == 0) // => false
console.log(null >= 0) // => ?
最后一个null >= 0
的结果为true。
刚看到这个代码的时候,下意识地会认为结果应该也是false
,毕竟上边标明了三种情况都为false
。
然而这个就露出了相等运算符和关系运算符两者执行的差异。
在相等运算符中,如果是非严格相等,则会尝试将两边的值转换为相同类型进行比较。
在关系运算符中,会尝试将运算符两边的值转换为Number
再进行比较。
所以在执行null >= 0
的时候null
被转换为Number
随后值就变为了0
,所以第四个运算符实际的执行为0 >= 0
。
觉得这个题挺有意思的,所以就去翻了下文档,看看这几个比较运算符在执行的时候都做了些什么。
相等运算符
相等运算符有四个,==
、!=
、===
、!==
,前两个会对运算符两边的表达式进行类型转换,试图转换为相同的类型。
==
与!=
执行时会先检查两者类型是否一致,如一致则相当于调用===
、!==
随后判断两者是否都为null
或undefined
,如果均为这两个值,则会直接返回true
接下来就会进行一些类型转换,绝大多数情况是会转换为Number
,但是主要转换类型的依赖还是在于运算符左侧表达式的类型。
如果一边类型为String
另一边类型为Number
,则会将String
转换为Number
对两者进行比较。
如果其中一个为Boolean
,则会将该表达式转换为Number
上边的是一些比较常规的类型转换,但是如果都不满足上边的条件,后续还会有其他的转换。
如果其中一个为Object
,另一个类型为Number
、String
或者是Symbol
中的任意一个。
则会获取Object
的原始值,然后对两者进行比较。
然后表格中对Object
类型又有一些额外的处理
在最后我们可以看到,会针对Object
类型的变量进行调用valueOf
与toString
而两个函数调用的顺序取决于上边一些判断的过程,目前还木有找到会先执行toString
的例子。。。(因为原始类型无法直接添加toString
和valueOf
事件的代理)
我们可以用Object.assign
来实现某个对象的toString
和valueOf
方法来观察执行的过程。
如果valueOf
返回值还是Object
的话,则会继续调用toString
如果两个函数都返回Object
,这时就会抛出一个类型异常的错误
===
与!==
相较==
,===
的逻辑就很清晰了,因为没有了不同类型之间的转换,就是拿到两个表达式进行比较即可。
首先就是获取两侧表达式的类型,如果不同则返回false
,相同则进行后续的比较。
关于Number
类型步骤的描述,有一点我很是疑惑,就是关于+0 === -0
,因为一元正负运算符的优先级肯定是高于===
的,不知为何会写在这里-.-
关系运算符
关系运算符的执行过程,是尽可能的将两边的表达式转换为Number
进行比较。(也确实,其他类型木有什么可比性的)
运算符刚开始会尝试将两侧表达式转换为原始值,并且在转换的过程中会优先选择转换为Number
类型。
转换完成后,如果两边表达式都为String
,则会先判断一侧表达式是否包含另一侧。
例如:
'abc' > 'ab' // abc 包含 ab 所以 abc 比 ab 大,结果为true
如果两者不为包含关系,则会从第一个字符开始获取对应的Unicode
编码,来进行比大小,如果大小相同,则顺移至下一位。
其余情况下,则会将两侧表达式直接转换为Number
求值。
Number(true)
Number({})
Number(undefined)
Number(null)
// ...
当任意一个结果为NaN
时,运算符的结果都为false
(而且文档中给出的,返回值为undefined
,并不是false
。。。)
然后针对<
、>
、<=
、>=
进行各自的判断。
所以到最后就解释了,为什么那个问题的null >= 0
为true
。
因为关系运算符是会将值转换为Number
来进行比较的。
而相等运算符只在极少数的情况下会将值转换为Number
来进行比较(例如:一个为Number
另一个为String
)