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)