引子
本文讲述为 ECMA-262 中描述可用于操作数据值的操作符中的 关系操作符;
关系操作符
关系操作符执行比较两个值的操作,包括小于(<)、大于(>)、小于等于(<=)和大于等于(>=),用法跟数学课上学的一样;
常规写法
这几个操作符都返回布尔值,如下所示:
let a = 10 > 3; // true
let b = 22 < 2; // false
类型转换规则
与 ECMAScript 中的其他操作符一样,在将它们应用到不同数据类型时也会发生类型转换和其他行为:
- 如果操作数都是数值,则执行数值比较
- 如果操作数都是字符串,则逐个比较字符串中对应字符的编码
- 如果有任一操作数是数值,则将另一个操作数转换为数值,执行数值比较
- 如果有任一操作数是对象,则调用其
valueOf()方法(如果没有则调用toString()方法),取得结果后再根据前面的规则执行比较 - 如果有任一操作数是布尔值,则将其转换为数值再执行比较
小知识
在使用关系操作符比较两个字符串时,会发生一个有趣的现象:
很多人认为小于意味着“字母顺序靠前”,而大于意味着“字母顺序靠后”,实际上不是这么回事;对字符串而言,关系操作符会比较字符串中对应字符的编码,而这些编码是数值,比较完之后,会返回布尔值。
🕳️问题1
大写字母的编码都小于小写字母的编码,因此以下这种情况就会发生:
let result = "Brick" < "alphabet"; // true
在这里,字符串"Brick"被认为小于字符串"alphabet",因为字母 B 的编码是 66,字母 a 的编码是 97。
🥚解决
要得到按字母顺序比较的结果,就必须把两者都转换为相同的大小写形式(全大写或全小写) ,然后再比较:
let result = "Brick".toLowerCase() < "alphabet".toLowerCase(); // false
将两个操作数都转换为小写,就能保证按照字母表顺序判定"alphabet"在"Brick"前头。
🕳️问题2
另一个奇怪的现象是在比较两个数值字符串的时候,比如下面这个例子:
let result = "23" < "3"; // true
因为两个操作数都是字符串,所以会逐个比较它们的字符编码(字符"2"的编码是 50,而字符"3"的编码是 51);
🥚解决
不过,如果有一个操作数是数值,那么比较的结果就对了:
let result = "23" > 3; // true
因为这次会将字符串"23"转换为数值 23,然后再跟 3 比较,结果当然对了。
只要是数值和字符串比较,字符串就会先被转换为数值,然后进行数值比较
🕳️问题3
对于数值字符串而言,这样能保证结果正确, 但如果字符串不能转换成数值呢? 如下这个例子:
let result = "a" < 3;
// 因为"a"会转换为 NaN,所以结果是 false
因为字符"a"不能转换成任何有意义的数值,所以只能转换为 NaN;
这里有一个规则,任何关系操作符在涉及比较
NaN时都返回false
这样一来,下面的例子有趣了:
let result1 = NaN < 3; // false
let result2 = NaN >= 3; // false
在大多数比较的场景中,如果一个值不小于另一个值,那就一定大于或等于它;但 在比较 NaN 时,无论是小于还是大于等于,比较的结果都会返回 false;
总结
关系操作符为执行比较两个值的操作;有四种操作符分别为:小于(<)、大于(>)、小于等于(<=)和大于等于(>=); 当操作数都是字符串比较编码;当操作数都是字符串比较编码数;当比较中有任一是数值,把另一个转换为数值;以及任何关系操作符在涉及比较 NaN 时都返回 false;