我很熟悉在 JavaScript 中 NaN是“奇怪的”,也就是说,NaN === NaN总是返回 false,正如所描述的 给你。因此,不应该进行 ===比较来检查 NaN,而应该使用 isNaN (。.)取而代之。
NaN
NaN === NaN
false
===
所以我很惊讶地发现
> [NaN].includes(NaN) true
这看起来不一致,为什么会有这种行为?
它是如何工作的? includes方法是否专门检查 isNaN?
includes
isNaN
正如你所看到的读取 包括文件,它确实使用 sameValueZero算法工作,因此,正如它的 文件所说,当比较 NaN 和 I 时,它给出一个 True值,引用:
sameValueZero
True
我们可以从下面的相同性比较表中看到,这是由于 Object.is处理 NaN 的方式造成的。注意,如果 Object.is (NaN,NaN)被评估为 false,我们可以说它符合松散/严格范围,作为更严格的三重等于形式,它区分 -0和 + 0。然而,NaN 处理意味着这是不正确的。不幸的是,我们必须从 Object.is的特定特征来考虑它,而不是从它对于相等运算符的松散性或严格性来考虑它。
Object.is
.includes()方法使用 SameValueZero算法来检查两个值的相等性,它认为 NaN值等于它自己。
.includes()
SameValueZero
SameValueZero算法类似于 SameValue,但唯一的区别是 SameValueZero算法认为 +0和 -0是相等的。
SameValue
+0
-0
Object.is()方法使用 SameValue,对于 NaN返回 true。
Object.is()
console.log(Object.is(NaN, NaN));
The behavior of .includes() method is slightly different from the .indexOf() method; the .indexOf() method uses strict equality comparison to compare values and strict equality comparison doesn't consider NaN to be equal to itself.
.indexOf()
console.log([NaN].indexOf(NaN));
关于不同的相等性检查算法的信息可以在 MDN 上找到:
等式比较和相同性
在 7.2.16严格的平等比较中,有以下注释:
注意 该算法在处理有符号零和 NaN 方面不同于 SameValue算法。
注意
该算法在处理有符号零和 NaN 方面不同于 SameValue算法。
这意味着 Array#includes的比较函数与严格的比较函数不同:
Array#includes
22.1.3.13 Array.Prototype.include
注3 包括方法故意在两个方面不同于类似的 索引方法。首先,它使用 SameValueZero算法,而不是 严格的平等比较,允许它检测 不阵列元素。其次,它不会跳过缺少的数组元素,而是将它们视为 未定义。
注3
包括方法故意在两个方面不同于类似的 索引方法。首先,它使用 SameValueZero算法,而不是 严格的平等比较,允许它检测 不阵列元素。其次,它不会跳过缺少的数组元素,而是将它们视为 未定义。
这似乎是 Number::sameValueZero抽象操作的一部分:
Number::sameValueZero
6.1.6.1.15编号: : sameValueZero (X,嘿) 如果 X是 不,嘿是 不,返回 没错。 [...]
[...]
这项操作必须是 Array#includes()检查的一部分,Array#includes()检查的目的是:
Array#includes()
22.1.3.13 Array.Prototype.include (SearchElement[ ,来自索引]) [...] 重复,当 K < Len 让 ElementK成为? Get (哦,! ToString (K))的结果。 如果 SameValueZero (SearchElement,ElementK)是 没错,则返回 没错。 将 k 设置为 k + 1。 返回 假的。 [...]
其中,SameValueZero操作将在步骤2中委托给一个用于数字的函数:
7.2.12 SameValueZero (X,嘿) [...] 如果 Type (X)与 Type (嘿)不同,则返回 假的。 如果 Type (X)是 Number 或 BigInt,则 Return! Type (X) : : sameValueZero (X,嘿). 返回! SameValueNonNumeric (X,嘿)。
作为比较,Array#indexOf()将使用 严格的平等比较,这就是为什么它的行为不同:
Array#indexOf()
const arr = [NaN]; console.log(arr.includes(NaN)); // true console.log(arr.indexOf(NaN)); // -1
使用 SameValueZero进行比较的其他操作包括集合和映射:
const s = new Set(); s.add(NaN); s.add(NaN); console.log(s.size); // 1 console.log(s.has(NaN)); // true s.delete(NaN); console.log(s.size); // 0 console.log(s.has(NaN)); // false
const m = new Map(); m.set(NaN, "hello world"); m.set(NaN, "hello world"); console.log(m.size); // 1 console.log(m.has(NaN)); // true m.delete(NaN); console.log(m.size); // 0 console.log(m.has(NaN)); // false
但是 SameValueZero算法首先出现在 ECMAScript 6规范中更加冗长,它仍然有相同的意思,并且仍然有一个明确的意思:
7.2.10 SameValueZero (X,嘿) [...] 如果 Type (X)是 Number,则 如果 X是 不,嘿是 不,返回 没错。 [...]
7.2.10 SameValueZero (X,嘿)
ECMAScript 5.1只有一个 SameValue算法 ,它仍然将 NaN等同于 NaN。与 SameValueZero的唯一区别是如何处理 +0和 -0: SameValue为它们返回 false,而 SameValueZero返回 true。
true
SameValue主要用于内部对象操作,因此对于编写 JavaScript 代码来说几乎无关紧要。SameValue的许多用法都是在处理对象键时使用的,而且没有数值。
SameValue操作直接暴露在 ECMAScript 6中,因为这是 Object.is()使用的:
console.log(Object.is(NaN, NaN)); // true console.log(Object.is(+0, -0)); // false
略有兴趣的是,WeakMap和 WeakSet也使用 SameValue,而不是 Map和 Set用于比较的 SameValueZero。但是,WeakMap和 WeakSet只允许将对象作为唯一成员,因此试图添加 NaN或 +0或 WeakSet0或其他原语会导致错误。
WeakMap
WeakSet
Map
Set
根据 MDN 的文件的说法
注意: 从技术上讲,includes()使用 < strong > sameValueZero 确定是否找到给定元素的算法。
includes()
const x = NaN, y = NaN; console.log(x == y); // false -> using ‘loose’ equality console.log(x === y); // false -> using ‘strict’ equality console.log([x].indexOf(y)); // -1 (false) -> using ‘strict’ equality console.log(Object.is(x, y)); // true -> using ‘Same-value’ equality console.log([x].includes(y)); // true -> using ‘Same-value-zero’ equality