作者:Abinav Seelan 原文链接:blog.campvanilla.com/javascript-…
在掘金前端群里(添加掘金酱:juejinjiang 入群)问了大家一个问题:
null > 0为false,null == 0为false,为什么null>=0为true?
有说 == 不会尝试转型,所以 null == 0成立。但我记得不转型的是===啊。
也有说转换为布尔值都为假,那'' == 0?
还有位群友给出了Number(null)==0的写法,让表达式结果为 true。
但是大家都没有正面回答问题。
打开你的浏览器,按下f12,在 console 中输入
null > 0;
null == 0;
null >= 0;看看结果是什么?null >0 // falsenull == 0// falsenull >= 0 // true
神奇吗?一个值既不大于0,也不等于0,但它居然大于或等于0。
一定是我的打开方式有问题,换个浏览器试试!
算了,还是查查 ECMAScript 标准吧。
抽象关系比较算法
先看看
null > 0// false根据标准,> <操作符利用抽象关系比较算法判断表达式为true或者false。
- 调用
ToPrimitive(X, hint Number)。- 调用
ToPrimitive(y, hint Number)。- 如果
Type(Result(1))是字符串且Type(Result(2)是字符串,跳转到步骤16。- 调用
ToNumber(Result(1))。- 调用
ToNumber(Result(2))。- 如果
Result(4)是 NaN, 返回undefined。- 如果
Result(5)是 NaN, 返回undefined。- 如果
Result(4)和Result(5)是同样的数字,返回false。- 如果
Result(4)是+0而且Result(5)是-0,返回false。- 如果
Result(4)是-0并且Result(5)是+0,返回false。- 如果
Result(4)是+∞,返回false。- 如果
Result(5)是+∞,返回true。- 如果
Result(5)是-∞,返回false。- 如果
Result(5)是-∞,返回true。- 如果
Result(4)数学意义上的值小于Result数学意义上的值——注意这些值都是有穷的且不都为0——返回true。否则,返回false。- 如果
Result(2)是Result(1)的前缀,返回false(如果字符串 q 可以由 字符串 p 后跟另一个字符串 r 连接组成,那么字符串 p 就是字符串 q 的前缀。注意任何一个字符串都是其本身的前缀,因为字符串 r 可以是空字符串)。- 如果
Result(1)是Result(2)的前缀,返回true。- 让 k 是最小的非负整数,这样
Result(1)在 k 位置的字符和Result(2)在 k 位置的字符会不一样。(因为两个字符串都不是另一个字符串的前缀,所以一定存在这个 k)- 让 m 是
Result(1)中 k 位置字符的编码值。- 让 n 是
Result(2)中 k 位置字符的编码值。
21.如果m<n,返回true。否则,返回false。
将null>0在整个算法中过一遍。
步骤一与步骤二 在null和0上调用ToPrimitive()分别将这两个值转换为原始类型(比如Number和String)。ToPrimitive的结果如下表:
| Input type | Result |
|---|---|
| Undefined | 不转换 |
| Null | 不转换 |
| Boolean | 不转换 |
| Number | 不转换 |
| String | 不转换 |
| Object | 转换为对象的默认值。对象的默认值通过调用内部的[[DefaultValue]]方法获得,忽略 hint 参数。 |
根据上表,null和0都没有转换。
所以步骤三对我们就不适用了,跳过步骤三。步骤四与步骤五需要将左右值均转换为Number类型。Number转换规则如下:
| Input Type | Result |
|---|---|
| Undefined | NaN |
| Null | +0 |
| Number | 不转换 |
| Boolean | true 转为1 ,false 转为 +0 |
| ... | ... |
(String 及 Object 的转换省掉了,因为暂时用不上。如果你好奇可以看这个🙃)
null转换为+0而0还是0。两个值都不是NaN,所以步骤六与步骤七可以跳过。步骤八要注意下,+0等于0,所以算法返回false。所以,
null > 0;//false
and
null < 0;//alse false抽象相等比较算法
现在再来看看,null == 0//false
这也非常有趣。
==操作符利用抽象相等比较算法判断true或者false。
- 如果
Type(x)与Type(y)不一致,跳转至步骤十四。- 如果
Type(x)是undefined, 返回true。- 如果
Type(x)是Null,返回true。- 如果
Type(x)不是数字,跳转到步骤十一。- 如果
x是NaN, 返回false。- 如果
y是NaN, 返回false。- 如果
x和y是同一个数,返回true。- 如果
x是+0且y是-0,返回true。- 如果
x是-0且y是+0,返回true。- 返回
false。- 如果
Type(x)是字符串,当x与y是完全相同的序列时(长度一致,对应位置的字符也一样。)返回true。否则返回false。- 如果
Type(x)是布尔值,当x与y都为true或都为false时,语句返回true,否则返回false。- 当
x与y指向同一个对象或指向的两个对象是联合对象(参见 13.1.2)时,返回true,否则返回false。- 如果
x是null且y是undefined,返回true。- 如果
x是undefined且y是null,返回true。- 如果
Type(x)是Number且Type(y)是字符串,返回x == [ToNumber](http://interglacial.com/javascript_spec/a-9.html#a-9.3)(y)的结果。- 如果
Type(x)是String且Type(y)是数字,返回[ToNumber](http://interglacial.com/javascript_spec/a-9.html#a-9.3)(x)==y的结果。- 如果
Type(x)是布尔值,返回ToNumber(x) == y的结果。- 如果
Type(y)是布尔值,返回x ==ToNumber(y)的结果。- 如果
Type(x)既不是字符串也不是数字而Type(y)是对象,返回x == ToPrimitive(y)的结果。- 如果
Type(x)是对象而Type(y)是字符串或数字,返回ToPrimitive(x) == y。- 返回
false。
判断 null 与 0 是否相等,我们立刻从步骤一跳到步骤十四。因为 Type 不一样。而且,因为Type(x) 是 null,所以步骤十四到步骤二十一也不适用。最终,步骤二十二默认返回 false。
所以,null == 0 ; //false
大于等于操作符(>=)
现在,我们来看最后一个表达式。null > 0;// true
这一块标准完全把我整蒙了。从宏观看,>=等价于
如果
null < 0是false,那么null>=0是true。
所以,null >= 0; //true
老实说,这是有道理的。数学上讲,如果两个数字x和y,x 不小于 y,那么x 必须大于或等于y。
我猜这么做的目的是为了优化比较表达式。如果可以一次比较——比较x小于y 是否成立,用这个结果推出原始表达式的结果。那干嘛还要去先比较x 是不是大于y,如果不是,再比较x是不是等于y呢。
(如果你对>= 操作符的实际运算步骤感兴趣,可以看这个)
在探索这个问题的过程中,对这门语言又有了更深的认识。希望这篇文章可以帮助你。