比较算法
在JavaScript中,有这些场景涉及到数据的比较:
a == b
a === b
[1, 2, 3].indexOf(1)
Object.is(a, b)
Set/Map
等集合中内部判断元素唯一性
在比较时,主要的差异就是数值的处理,除了数值类型,其他类型的比较比较简单:
undefined = undefined
null = null
true = true
false = false
'abc' = 'abc'
- Symbol、对象判断引用地址是否相等
而数值由于有很多边界值,因此要考虑的问题更多:
NaN
等于NaN
吗?+0
等于-0
吗?Infinity
等于Infinity
吗?
除了数值类型比较的差异,非数值类型的比较算法得到了统一,这个算法就是 SameValueNonNumber 。
各场景下所使用的算法及细节如下表所示:
场景 | 算法 | 说明 | 数值比较算法 | 非数值比较算法 |
---|---|---|---|---|
a === b | IsStrictlyEqual | 严格相等 | Number::equal | SameValueNonNumber |
a == b | IsLooselyEqual | 宽松相等 | / | / |
Object.is(a, b) | SameValue | 同值 | Number::sameValue | SameValueNonNumber |
new Set([1, 1]) | SameValueZero | 同值(包括零) | Number::sameValueZero | SameValueNonNumber |
四个底层算法
接下来,我们先学习SameValueNonNumber、Number::equal、Number::sameValue、Number::sameValueZero这四个底层比较算法。
SameValueNonNumber(x, y)
- 比较细节
- x、y必须同一类型
- x、y同为
undefined
或null
,返回true
- x、y为boolean,则同为
true
或false
,返回true
- x、y为bigint,则使用Bigint的
equal
算法 - x、y为字符串,同样长度且每个索引位置的字符一样,返回
true
- 其他类型则直接判断指针地址,相同,返回
true
Number::equal
- 比较细节:
NaN
不等于NaN
-0
与+0
相等- 其他数值按照是否相等判断
- 相等算法将
-0
与+0
视为相等,但不同的NaN
视为不相等
Number::sameValue
- 比较细节:
NaN
等于NaN
-0
与+0
互不相等- 其他数值按照是否相等判断
- 同值算法将不同的
NaN
值视为同一个值,不包含不同情况的零值
Number::sameValueZero
- 比较细节:
NaN
等于NaN
-0
与+0
相等- 其他数值按照是否相等判断
- 同值(含零)算法不仅将不同的
NaN
视为同一个值,并且将不同的零值视为同一个值
Number::equal 、Number::sameValue 、Number::sameValueZero 三个算法的差别在于对 NaN
、-0
、+0
的不同处理。
不同比较算法细节
SameValue(x, y)
- 如果类型不同,直接返回
false
- 如果x为数值,进行
Number::sameValue
比较 - 如果不为数值,则进行
SameValueNonNumber
比较
SameValueZero(x, y)
- 如果x、y类型不同,返回
false
- 如果x为数值,进行
Number::sameValueZero
比较 - 如果不为数值,则进行
SameValueNonNumber
比较
IsStrictlyEqual(x, y)
- 如果x、y类型不同,返回
false
- 如果x为数值,进行
Number::equal
比较 - 如果不为数值,进行
SameValueNonNumber
比较
IsLooselyEqual(x, y)
- 比较细节:
- 如果x、y类型相同,进行
IsStrictlyEqual
比较 - 如果x、y分别为
undefined
或null
,相等 - x、y一个为数值,一个为字符串时,将字符串进行
ToNumber
操作转成数值,然后重新进行IsLooselyEqual
比较 - x、y一个为bigint类型,一个为字符串时,将字符串转成bigint,然后重新进行
IsLooselyEqual
比较 - x、y一个为布尔值,一个为字符串时,将布尔值转成数值,然后重新进行
IsLooselyEqual
比较 - x、y中有一个为对象时,先执行
ToPrimitive
操作转基本数据类型,然后重新进行IsLooselyEqual
比较 - x、y一个为数值,一个为bigint时,两个值不为无穷且相等时,则x等于y
- 如果x、y类型相同,进行
- 练习题:
'1' == 1 // true
true == 1 // true
false == 0 // true
'NaN' == NaN // false
NaN == true // false
NaN == false // false
'0' == false // true
- 宽松相等比较算法的核心是将非数值类型转为数值,再进行严格相等对比
经验
NaN
是JavaScript中唯一的自身不等于自身的值- 宽松比较时,如果一个值为undefined或null,另一个只要不为undefined或null,则比较结果一定为
false
练习
javascript
const pairs = [
[undefined, undefined],
[null, null],
[true, true],
[true, false],
[false, false],
[1, 1],
[NaN, NaN],
[Infinity, NaN],
[-0, +0],
[Symbol.iterator, Symbol.iterator],
[Symbol(), Symbol()],
['', ' '],
['1', '1'],
[{ valueOf() { return 1 } }, { valueOf() { return 1 } }],
[[1], [1]],
// undefined
[undefined, null],
[undefined, true],
[undefined, false],
[undefined, 1],
[undefined, 0],
[undefined, -0],
[undefined, +0],
[undefined, NaN],
[undefined, Infinity],
[undefined, Symbol.iterator],
[undefined, ''],
[undefined, ' '],
[undefined, '1'],
[undefined, 'false'],
[undefined, {}],
[undefined, { toString() { return undefined } }],
[undefined, { valueOf() { return 1 } }],
// null
[null, true],
[null, false],
[null, 1],
[null, 0],
[null, -0],
[null, +0],
[null, NaN],
[null, Infinity],
[null, Symbol.iterator],
[null, ''],
[null, ' '],
[null, '1'],
[null, 'false'],
[null, {}],
[null, { toString() { return null } }],
[null, { valueOf() { return 1 } }],
// boolean
[true, 1],
[true, 0],
[true, 2],
[true, NaN],
[false, NaN],
[true, Infinity],
[true, -0],
[true, +0],
[false, -0],
[false, +0],
[true, Symbol.iterator],
[true, ''],
[false, ''],
[true, ' '],
[false, ' '],
[true, '0'],
[true, '1'],
[false, '0'],
[false, '1'],
[true, {}],
[true, { toString() { return true } }],
[true, { toString() { return 1 } }],
[true, []],
[false, []],
[true, [1]],
[false, [1]],
[true, [0]],
[false, [0]],
[true, [0, 1]],
[false, [0, 1]],
// number
[1, '1'],
[0, '0'],
[0, '1'],
[-0, '-0'],
[+0, '+0'],
[NaN, 'NaN'],
[NaN, '1'],
[Infinity, 'Infinity'],
[-Infinity, '-Infinity'],
[0, ''],
[0, ' '],
[1, { valueOf() { return 1 } }],
[1, { valueOf() { return '1' } }],
[1, { toString() { return 1 } }],
[1, { toString() { return '1' } }],
[1, [0]],
[1, [1]],
[1, [1, 2]],
// symbol
[Symbol.iterator, undefined],
[Symbol.iterator, null],
[Symbol.iterator, true],
[Symbol.iterator, false],
[Symbol.iterator, 0],
[Symbol.iterator, -0],
[Symbol.iterator, +0],
[Symbol.iterator, NaN],
[Symbol.iterator, Infinity],
[Symbol.iterator, -Infinity],
[Symbol.iterator, ''],
[Symbol.iterator, ' '],
[Symbol.iterator, { toString() { return Symbol.iterator } }], // true
// string
['undefined', undefined],
['null', null],
['', false],
[' ', false],
['', 0],
['', -0],
['', +0],
['', NaN],
['', Infinity],
[' ', 0],
[' ', -0],
[' ', +0],
[' ', NaN],
[' ', Infinity],
['1', 1],
[' 1 ', 1],
['NaN', NaN],
['', {}],
['', []],
[' ', {}],
[' ', []], // false [] -> '' / '' != ' '
['1', [1]],
['1', [1, 2]],
['1', { valueOf() { return 1 } }],
['1', { toString() { return 1 } }],
['1', { valueOf() { return 1 }, toString() { return 2 } }],
// object
[{}, {}],
[{ valueOf() { return 1 } }, { toString() { return 1 } }],
[{ valueOf() { return '1' } }, [1]],
[[1], [1]],
[{ valueOf() { return undefined } }, undefined],
[{ valueOf() { return null } }, null],
[{ valueOf() { return 1 } }, '1'],
[{ toString() { return '1' } }, true],
[[1], undefined],
[[0], undefined],
[[0], null],
[[0], false],
[[0], 0],
[[0], -0],
[[0], +0],
[[NaN], NaN],
[[Infinity], Infinity],
]