JavaScript 中的相等性判断研究
在 JavaScript 中,理解相等性判断的细微差别对于编写正确和高效的代码至关重要。本文将深入探讨 JavaScript 中的三种相等性判断方式,详细阐述其中的难点和易错点。
一、JavaScript 中的三种值比较运算
JavaScript 提供了三种不同的值比较运算:
- 严格相等(
===
):不进行类型转换,如果类型不同,直接返回false
。 - 宽松相等(
==
):会进行类型转换,根据特定规则比较值。 Object.is()
:不进行类型转换,也不对NaN
、+0
和-0
进行特殊处理。
二、对应的算法
上述三个操作分别对应于 JavaScript 中的四个相等算法中的三个:
IsLooselyEqual
:对应==
。IsStrictlyEqual
:对应===
。SameValue
:对应Object.is()
。SameValueZero
:用于许多内置运算,例如Array.prototype.includes()
。
三、使用 ===
进行严格相等比较
严格相等(===
)比较非引用类型时,如果两个值类型不同,直接返回 false
;如果类型相同,值也相同,且都不是 number
类型,返回 true
。对于 number
类型,如果两个值都是数值相同的非 NaN
数字,包括 +0
和 -0
,则返回 true
。NaN
与任何值(包括自身)都不全等,因此 NaN
=== NaN
返回 false
。+0
与 -0
被认为是相等的,因为在 JavaScript 中,+0 === -0
返回 true。对于引用类型, 如果两个对象的内容相同,内容不同也是返回false
。
易错点:
NaN
与任何值都不全等,包括自身。 因此,NaN === NaN
返回false
。+0
与-0
被认为是相等的。 在大多数情况下,这符合直觉,但在特殊情况下可能导致问题。number
类型需要特殊判断,其他都是判断类型和值是否相等。- 对象比较的是引用:和
Object.is()
比较对象的行为一致,如果两个对象内容相同,但是引用不同,那么会返回false。
示例:
console.log(NaN === NaN); // false
console.log(+0 === -0); // true
注意事项:
- 数组索引查找方法使用严格相等。 例如,
Array.prototype.indexOf()
无法找到NaN
的索引,因为NaN !== NaN
。
四、使用 ==
进行宽松相等比较
宽松相等(==
)在比较前会对操作数进行类型转换,其规则较为复杂,容易出错。
类型转换规则:
null
与undefined
:null == undefined
和undefined == undefined
和null == null
返回true
,null == other
都为false。- 布尔值转换为数字:
true
转换为1
,false
转换为0
。 - 字符串与数字比较:字符串转换为数字。
- 对象与原始类型比较:对象转换为原始值(调用
valueOf
或toString
方法)。
易错点:
- 可能导致非预期的类型转换。 例如,
[] == false
返回true
,因为[]
转换为""
,再转换为0
。 NaN
与任何值都不相等,包括自身。 因此,NaN == NaN
返回false
。- 引用类型的转换:不会比较引用对象的地址,而是将其转换为字符串、数字进行比较,所以再非严格条件下的相同内容的不同引用对象将会返回
true
示例:
console.log([] == false); // true
console.log('' == 0); // true
console.log(null == undefined); // true
不推荐使用宽松相等的原因:
- 容易导致难以察觉的错误。
- 代码可读性差,维护困难。
五、使用 Object.is()
进行同值相等比较
Object.is()
与 ===
类似,但有以下区别:
-0
与+0
不相等。Object.is(+0, -0)
返回false
。NaN
被认为等于自身。Object.is(NaN, NaN)
返回true
。- 对象比较的是引用 如果两个对象内容相同,但是引用不同也会返回false
// Object.is()方法
Object.is(0, -0) // false
Object.is(NaN, NaN) // true
const obj_1 = {
name: "Jack"
}
const obj_2 = {
name: "Jack"
}
// 对于对象比较的是引用地址
console.log(Object.is(obj_1, obj_2)) // false\
// 对于非引用类型,就是先比较类型,再比较值
console.log(Object.is(obj_1.name, obj_2.name)) // true
使用场景:
- 需要区分
-0
和+0
的情况。 - 需要判断
NaN
是否存在。
示例:
console.log(Object.is(+0, -0)); // false
console.log(Object.is(NaN, NaN)); // true
六、SameValueZero 相等性
SameValueZero 类似于 Object.is()
,但认为 +0
与 -0
相等。这种相等性用于内部算法,如 Array.prototype.includes()
、Map
和 Set
的比较。
实现方式:
function sameValueZero(x, y) {
// x !== x && y !== y 其实就是isNaN(x) && isNaN(y)
return x === y || (x !== x && y !== y);
}
注意事项:
NaN
被认为等于自身。+0
与-0
被认为相等。x !== x && y !== y
其实就是isNaN(x) && isNaN(y)
Array.peototype.includes()
内部实现使用零值相等算法,所以可以查找NaN
七、相等性方法的比较
下表总结了不同相等性方法的比较结果:
x | y | x == y | x === y | Object.is(x, y) | SameValueZero |
---|---|---|---|---|---|
undefined | undefined | ✅ | ✅ | ✅ | ✅ |
null | null | ✅ | ✅ | ✅ | ✅ |
NaN | NaN | ❌ | ❌ | ✅ | ✅ |
+0 | -0 | ✅ | ✅ | ❌ | ✅ |
'0' | 0 | ✅ | ❌ | ❌ | ❌ |
false | 0 | ✅ | ❌ | ❌ | ❌ |
{} | {} | ❌ | ❌ | ❌ | ❌ |
[] | false | ✅ | ❌ | ❌ | ❌ |
null | undefined | ✅ | ❌ | ❌ | ❌ |
易错点:
NaN
的比较在Object.is
和SameValueZero
中返回true
。+0
与-0
在Object.is
中返回false
,在其他比较中返回true
。
八、何时使用 Object.is()
而非 ===
建议:
- 除非需要区分
+0
和-0
,否则使用===
更为合适。 - 在处理不可变属性或需要精确比较的情况下,使用
Object.is()
。
九、特殊情况下的 -0
和 NaN
可能引入 -0
的操作:
- 一元负号(
-
):-0
可能由-0
或-(-0)
得到。 - 数学函数:
Math.atan2
、Math.ceil
、Math.pow
、Math.round
等可能返回-0
。 - 位运算:
~
、<<
、>>
等可能导致-0
消失。 - 乘法和除法运算:0 * -1 和 0 / -1 都会得到 -0。
示例
// 一元符号演示
let negativeZero = -0
console.log(negativeZero)
let positiveZero = -(-0)
console.log(positiveZero)
console.log(Object.is(negativeZero, positiveZero)) // false
// 数学函数
let ceilRes = Math.ceil(-0.5)
console.log(ceilRes) // -0
// 乘法和除法运算 产生-0
let multiplyRes = 0 * -1
console.log(multiplyRes) // -0
let divideRes = 0 / -1
console.log(divideRes) // -0
注意事项:
- 在涉及浮点数运算时,需要小心处理
-0
。 NaN
有多种表示形式,但在 JavaScript 中被认为是相同的。