值的比较

值的比较

Javascript中,比较大小使用大于(>)、小于(<)、大于等于(>=)、小于等于(<=) 4个比较运算符,比较相等使用普通相等(==)和严格相等(===)

这里分成比较大小和比较相等,这两者是独立的,是两套独立的比较机制

先看一个例子,是不是很奇怪?看到后面就知道咋回事了

1
2
3
null > 0 // false
null == 0 // false
null >= 0 // true

比较的结果都返回布尔值,要么true,要么false

1
2
3
2 > 1 // true
3 == 1 // false
2 != 1 // true

比较大小

比较大小有以下几种情况

  • 如果比较双方都是字符串,则根据它们所包含的 Unicode 码位的值,将它们作为字符串进行比较。
    会按照下面的方式进行比较
    1.比较两个字符串的首位字符大小
    2.如果这两个字符大小不相等,比较结束
    3.否则继续比较下一位
    4.重复上述比较,直到有一个字符串到达最后一个字符
    5.如果到达最后一个字符两者还相等,则判定两个字符串相等,否则还有剩余字符的字符串更大
    看几个例子

    1
    2
    'aa' < 'ab' // true 第一个字符a相等,比较第二个字符,a的Unicode码的值比b小
    'abcd' > 'abc' // true 在比较完abc之后,前面的字符还有剩余字符d
  • 比较双方不都是字符串时,会将两边的操作数转化为数值类型(可以当做Number类型)

    1. 对象会依次调用其[Symbol.toPrimitive]()valueOf()toString()方法先将对象转换为基本类型,然后如果转换后的基本类型不是数值类型,再进一步转换为数值类型

      [Symbol.toPrimitive]()可以自定义对象在强制转换为基本类型时如何返回基本类型值,可在MDN上查看

      这里需要注意的是,依次调用[Symbol.toPrimitive]()valueOf()toString()时,如果前一个返回的结果不是基本类型,会继续调用下一个,如果调用toString()之后返回的还不是基本类型,进行判断时会报错

      1
      2
      3
      4
      5
      6
      var arr = [];
      // arr.valueOf()返回的是[],会继续走到toString(),手动改一下toString()就行
      arr.toString = function() {
      return []
      }
      console.log(arr > 1) // 报错 Uncaught TypeError: Cannot convert object to primitive value
    2. 布尔值 truefalse 分别转化为 1 和 0。

    3. null 转化为 0。

    4. **undefined 转化为 NaN**。

    5. 字符串根据其包含的值进行转换,如果不包含数字值,则转换为 NaN

    注意:字符串在转换为数字时,会忽略两端的空白字符(空格、换行符\n、制表符\t等),然后尝试把剩下的字符转换为数字,相当于使用Number()函数去转换,转换失败返回NaN。最开始我以为只要剩下的字符包含了非数字字符就会返回NaN,实际上并不是,因为16进制也是可以转换的,下面的例子能够正常转换。

    1
    Number('   \n   0xa1   \n\t') // 161 0xa1表示16进制,会转换为10进制
  • 如果任意一个值为 NaN,则运算符返回 false。(NaN和任何值进行大小比较都会返回false,进行相等比较也会返回false,包括比较它自己)

  • 否则,这些值将作为数值进行比较。BigInt 和数值可以一起比较。

1
2
3
4
5
6
7
const obj = {
value: 123,
valueOf: () => '123'
}
console.olog(obj > 69) // true obj转换为基本类型为字符串123,然后再转换为数字123,再进行比较
console.log(true > 0) // true true转换为数字1
console.log(undefined > -1) // false // undefined转换为NaN,NaN和任何类型比较都返回false

比较相等

  • 普通相等(==)
  1. 如果比较双方类型相同,这样比较
  • 对象(Object):仅当两个操作数引用同一个对象时返回 true
  • 字符串(String):仅当两个操作数具有相同的字符且顺序相同时返回 true
  • 数字(Number):如果两个操作数的值相同,则返回 true+0-0 被视为相同的值。如果任何一个操作数是 NaN,返回 falseNaN 永远不等于 NaN
  • 布尔值(Boolean):仅当操作数都为 true 或都为 false 时返回 true
  • 大整型(BigInt):仅当两个操作数的值相同时返回 true
  • 符号(Symbol):仅当两个操作数引用相同的符号时返回 true
  1. 如果其中一个操作数为 nullundefined,另一个操作数也必须为 nullundefined 以返回 true。否则返回 false

  2. 如果其中一个操作数是对象,另一个是原始值,则将对象转换为原始值。

  3. 在这一步,两个操作数都被转换为原始值(字符串、数字、布尔值、符号和大整型中的一个)。剩余的转换将分情况完成。

  • 如果是相同的类型,使用步骤 1 进行比较。

  • 如果其中一个操作数是符号而另一个不是,返回 false

  • 如果其中一个操作数是布尔值而另一个不是,则将布尔值转换为数字:true 转换为 1,false 转换为 0。然后再次对两个操作数进行普通相等比较。

  • 数字与字符串:将字符串转换为数字。转换失败将导致 NaN。

  • 数字与大整型:按数值进行比较。如果数字的值为 ±∞ 或 NaN,返回 false。

  • 字符串与大整型:使用与 BigInt() 构造函数相同的算法将字符串转换为大整型数。如果转换失败,返回 false。

  • 严格相等(===)

相较于普通相等,严格相等不会做任何的类型转换,如果两者类型不同,直接返回false

还有一个Object.js()方法也可以判断两个值是否相等,它和严格相等唯一的区别有两点:1.带符号的0和NaN

1
2
3
4
5
+0 === -0 // true
Object.is(+0, -0) // false

NaN === NaN // false
Object.is(NaN, NaN) // true

好了,看一下最开始的问题,现在应该知道答案了

1
2
3
null > 0 // false
null == 0 // false
null >= 0 // true
  1. null > 0 ;这里是进行大小比较,两边都会转换为数值,null转换为0,0 > 0为false
  2. null == 0;这里是进行普通相等比较,null和undefined在进行普通相等比较时,只有它们两者会相互相等,所以这里也是false
  3. null >= 0;这里进行大小比较,两边都会转换为数值,null转换为0,0 >= 0,返回true

来几道题练练手

题目1:判断以下比较的结果

  • [] == ![]
  • [] == []
  • [1,2] == “1,2”
  • [1,2] > [1,3]
  • [1,2] > null

题目2:判断以下比较的结果

  • 2 > true > false
  • 2 > true > 1
  • undefined > 0 > null

题目3:判断以下比较的结果

1
2
3
4
5
6
7
8
9
10
11
12
const obj1 = {
[Symbol.toPrimitive](hint) {
return hint === 'number' ? 1 : '2';
}
};
const obj2 = {
valueOf() { return 2; },
toString() { return '1'; }
};
obj1 > obj2
obj1 == obj2
+obj1 > obj2

题目4:判断以下比较的结果

  • Number.MIN_VALUE > 0
  • Number.MIN_VALUE < 0
  • -Number.MIN_VALUE > 0
  • -Number.MIN_VALUE < 0

题目5:判断以下比较的结果

  • ‘0xff’ > 255
  • ‘0xff’ > ‘255’
  • 255n > ‘254’
  • ‘255’ > 254n

题解:

题目1和题目2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[] == ![] // true,这里有一个逻辑运算符!(非),它的优先级在逻辑运算符里面最高,优先处理![],
这个运算符把紧接其后的操作数先转换为布尔类型,然后取反。
布尔型转换中,只有0, null, undefined, NaN, ""这五个转换为false,其余都为true。
这里的![]为false,然后判断[] == false,比较对象类型(数组也属于对象类型,也可以说成引用类型)和原始值时,对象类型需要转换为原始类型,
[]的valueOf()返回的还是[],调用toString()返回空字符串'',现在也就是比较'' == false,然后再把false转换为数字0,变成'' == 0,
然后字符串转换为数字,最后变成 0 == 0,返回true
[] == [] // false,这里比较的是引用地址
[1,2] == '1,2' // true,[1,2]调用valueOf()返回[1,2],继续调用toString()返回'1,2'
[1,2] > [1,3] // false 大小比较都转换为数值。valueOf()返回本身,调用toString()返回'1,2'和'1,3',然后转换为数值都是NaN
[1,2] > null // false,前面转换为NaN,后面都可以不用看
2 > true > false // true 2 > true返回true,变成true > false,转换为1和0比较
2 > true > 1 // false 2 > true返回true,变成true > 1,true转换为1,1 > 1 返回false
undefined > 0 > null // false undefined转换为NaN直接返回false,后面不用看了

题目3:

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj1 = {
[Symbol.toPrimitive](hint) {
return hint === 'number' ? 1 : '3';
}
};
const obj2 = {
valueOf() { return 2; },
toString() { return '1'; }
};
obj1 > obj2 // false 大小比较,都转换为数值。obj1调用Symbol.toPrimitive返回1,obj2调用valueOf()返回2,变成1 > 2
obj1 == obj2 // false 普通相等比较,比较的是引用地址
+obj1 > obj2 // false 大小比较,都转换为数值。+obj1期望的是数字,返回1,obj2调用valueOf()返回2,变成1 > 2
// 这里的obj1 > obj2和+obj1 > obj2中obj1都期望是数字,也就是hint为number,如果想要期望字符串,('' + obj1) > obj2,这样('' + obj1)返回的就是'3'

题目4:可以查看MDN上对于Number.MIN_VALUE的定义,它表示的是最小正数值。

1
2
3
4
Number.MIN_VALUE > 0 // true
Number.MIN_VALUE < 0 // false
-Number.MIN_VALUE > 0 // false
-Number.MIN_VALUE < 0 // true

题目5:

1
2
3
4
'0xff' > 255 // false '0xff'转换成255
'0xff' > '255' // false '0xff'转换成255,'255'转换成255
255n > '254' // true '254'转换成254,number类型可以直接和bigInt类型比较
'255' > 254n // true '255'转换成255,number类型可以直接和bigInt类型比较