请各位读者添加一下作者的微信公众号,以后有新的文章,将在微信公众号直接推送给各位,非常感谢。
1.前言
今天看见朋友们在讨论一个问题,说 null 到底和 0 是不是相等的。
听到这里,自己赶紧去写个 Demo 试一下。
MR_LP:3206064928
什么情况?
为什么 console.log(null <= 0);
和 console.log(null >= 0);
这两条的判断是 true 呢?
2.查阅资料
如果想明确,这个问题具体是怎么回事,那么我们需要重新来回顾一下我们的 ECMAScript Language Specification (HTML version),翻译过来就是ECMAScript语言规范(HTML版本)。
2.1 内部相等性运算算法
首先我们来看一下 ES3 关于 内部相等性运算的算法实现。
11.9.3 The Abstract Equality Comparison Algorithm
The comparison x == y, where x and y are values, produces true or false. Such a comparison is performed as follows:
- If Type(x) is different from Type(y), go to step 14.
- If Type(x) is Undefined, return true.
- If Type(x) is Null, return true.
- If Type(x) is not Number, go to step 11.
- If x is NaN, return false.
- If y is NaN, return false.
- If x is the same number value as y, return true.
- If x is +0 and y is -0, return true.
- If x is -0 and y is +0, return true.
- Return false.
- If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions). Otherwise, return false.
- If Type(x) is Boolean, return true if x and y are both true or both false. Otherwise, return false.
- Return true if x and y refer to the same object or if they refer to objects joined to each other (see 13.1.2). Otherwise, return false.
- If x is null and y is undefined, return true.
- If x is undefined and y is null, return true.
- If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
- If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x)== y.
- If Type(x) is Boolean, return the result of the comparison ToNumber(x)== y.
- If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
- If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
- If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x)== y.
- Return false.
2.2 内部关系运算算法
接下来我们再来看一下 ES3 关于 内部关系运算的算法实现。
11.8.5 The Abstract Relational Comparison Algorithm
The comparison x < y, where x and y are values, produces true, false, or undefined (which indicates that at least one operand is NaN). Such a comparison is performed as follows:
- Call ToPrimitive(x, hint Number).**
- Call ToPrimitive(y, hint Number).**
- If Type(Result(1)) is String and Type(Result(2)) is String, go to step 16. (Note that this step differs from step 7 in the algorithm for the addition operator **+
** in using and instead of or.) - Call ToNumber(Result(1)).
- Call ToNumber(Result(2)).
- If Result(4) is NaN, return undefined.
- If Result(5) is NaN, return undefined.
- If Result(4) and Result(5) are the same number value, return false.
- If Result(4) is +0 and Result(5) is -0, return false.
- If Result(4) is -0 and Result(5) is +0, return false.
- If Result(4) is +∞, return false.
- If Result(5) is +∞, return true.
- If Result(5) is -∞, return false.
- If Result(4) is -∞, return true.
- If the mathematical value of Result(4) is less than the mathematical value of Result(5) --- note that these mathematical values are both finite and not both zero --- return true. Otherwise, return false.
- If Result(2) is a prefix of Result(1), return false. (A string value p is a prefix of string value q if q can be the result of concatenating p and some other stringr. Note that any string is a prefix of itself, because r may be the empty string.)
- If Result(1) is a prefix of Result(2), return true.
- Let k be the smallest nonnegative integer such that the character at position k within Result(1) is different from the character at position k within Result(2). (There must be such a k, for neither string is a prefix of the other.)
- Let m be the integer that is the code point value for the character at position k within Result(1).
- Let n be the integer that is the code point value for the character at position k within Result(2).
- If m < n, return true. Otherwise, return false.
2.3 ES3 的 运算符
2.3.1 ES3 的 ">" 运算符:
The Greater-than Operator ( > )
The production RelationalExpression :
RelationalExpression > ShiftExpression is evaluated as follows:
- Evaluate RelationalExpression.
- Call GetValue(Result(1)).
- Evaluate ShiftExpression.
- Call GetValue(Result(3)).
- **Perform the comparison Result(4) < Result(2). **
- If Result(5) is undefined, return false. Otherwise, return Result(5).
2.3.2 ES3 的">=" 运算符:
The Greater-than-or-equal Operator ( >= )
The production RelationalExpression :
RelationalExpression >= ShiftExpression is evaluated as follows:
- Evaluate RelationalExpression.
- Call GetValue(Result(1)).
- Evaluate ShiftExpression.
- Call GetValue(Result(3)).
- Perform the comparison Result(2) < Result(4). (see 11.8.5).
- If Result(5) is true or undefined, return false. Otherwise, return true.
2.3.3 ES3 的 "==" 运算符 :
The Equals Operator ( == )
The production EqualityExpression :
EqualityExpression == RelationalExpression is evaluated as
follows:
- Evaluate EqualityExpression.
- Call GetValue(Result(1)).
- Evaluate RelationalExpression.
- Call GetValue(Result(3)).
- Perform the comparison Result(4) == Result(2). (see 11.9.3).
- Return Result(5).
3. 根据资料得出的内容
着重看一下,上面特意加粗的地方,我们可以明确下面三件事。
关系运算符 和 相等运算符 并不是一个类别的.
关系运算符,在设计上,总是需要运算元尝试转为一个number . 而相等运算符在设计上,则没有这方面的考虑.
最重要的一点, 不要把 拿 a > b , a == b 的结果 想当然的去和 a >= b 建立联系. 正确的符合最初设计思想的关系是 a > b 与 a >= b是一组 . a == b 和其他相等运算符才是一组. 比如 a === b , a != b, a !== b .
那么我们就可以反过来看这个问题了。
null > 0 // null 尝试转型为number , 则为0 . 所以结果为 false,
null >= 0 // null 尝试转为number ,则为0 , 结果为 true.
null == 0 // null在设计上,在此处不尝试转型. 所以 结果为false.
这里引用一下 Franky大大的话。
a >= b 运算符只是简单的去对 a < b的结果取反. 我以为这是一个设计上的失误的另一个理由是 undefined,在标准中,被单拎出来.细心的你,也一定发现了这一点. 对于undefined的设计, undefined > 0 , undefined < 0, undefined == 0 的结果是符合设计上,逻辑的一致性的. 而null是被遗漏的东西.直到今天早上.我重新翻阅了ES3,5.相关章节. 才恍然大悟自己没有从根本上理解到这个问题.
虽然前面的例子,我catch到了BE当初的设计思想. 但是从全局的角度来看. 从关系运算符到相等运算符,尤其是相等运算符的设计上. 真的十分混乱不堪. BE在信中提到,他对 == 的现状也很无奈. 甚至用愚蠢这个词来形容自己当初的实现(当然他还提到,当初只是为了在10天内设计出js,并跑过qa的测试用例). 即使如此, 但是他仍然表示 null == 0 这个结果是他想要的.
好吧,到了这里,我也有种无力感. 我认为纵观javascript,对关系运算和相等运算的设计.除了混乱,我想不出还有什么词来形容它们更恰当. 这一点从,我们生产环境代码中,大量的类型检查,和防御性代码的的存在,就可以证明这一点.
同时 Franky大大还举了另外一个例子。
function case1(a){
if(a == null){
....
}
}
function case2(a){
if(a == undefined){
...
}
}
// 上面两组完全等价, 这就是一种不明确表述.
// 我们永远不知道代码编写者的目的到底是同时匹配null 和 undefined还是只匹配其中某一个
function case3(a){
if(a === null || a === undefined){
...
}
}
// case3 才是最好的表述. 我们明确知道代码编写者的意图.
// 即使很多人可能认为这个代码很愚蠢. 但我坚定的认为这才是好代码.
最后, 不得不提到,我发出null >= 0 这封信后, Andrea Giammarchi 表示了对我之前看法的支持,他同我最初的看法一样,认为 null >= 0 的结果应该为 false . 并建议在 ES7 中的严格模式中,修改这个结果. 虽然同样遭到 David Bruant 的反对. 好吧为他和我的这个错误看法,默哀一分钟...
4.后记
所以写代码,写规范,都应该明确表述. 即使表述的很罗嗦,但不会引起歧义或怀疑. 这才是一份好的标准.文档,代码. 而避免歧义,和各种混乱不堪的规则,是一门语言最应遵守的设计原则.
最后也希望大家在日常的开发中,能够少遇坑。
李鹏(MR_LP : 3206064928)