为什么[ NaN ] . include (NaN)在 JavaScript 中返回 true?

我很熟悉在 JavaScript 中 NaN是“奇怪的”,也就是说,NaN === NaN总是返回 false,正如所描述的 给你。因此,不应该进行 ===比较来检查 NaN,而应该使用 isNaN (。.)取而代之。

所以我很惊讶地发现

> [NaN].includes(NaN)
true

这看起来不一致,为什么会有这种行为?

它是如何工作的? includes方法是否专门检查 isNaN

10191 次浏览

正如你所看到的读取 包括文件,它确实使用 sameValueZero算法工作,因此,正如它的 文件所说,当比较 NaN 和 I 时,它给出一个 True值,引用:

我们可以从下面的相同性比较表中看到,这是由于 Object.is处理 NaN 的方式造成的。注意,如果 Object.is (NaN,NaN)被评估为 false,我们可以说它符合松散/严格范围,作为更严格的三重等于形式,它区分 -0和 + 0。然而,NaN 处理意味着这是不正确的。不幸的是,我们必须从 Object.is的特定特征来考虑它,而不是从它对于相等运算符的松散性或严格性来考虑它。

.includes()方法使用 SameValueZero算法来检查两个值的相等性,它认为 NaN值等于它自己。

SameValueZero算法类似于 SameValue,但唯一的区别是 SameValueZero算法认为 +0-0是相等的。

Object.is()方法使用 SameValue,对于 NaN返回 true。

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.

console.log([NaN].indexOf(NaN));

关于不同的相等性检查算法的信息可以在 MDN 上找到:

等式比较和相同性

7.2.16严格的平等比较中,有以下注释:

注意

该算法在处理有符号零和 NaN 方面不同于 SameValue算法。

这意味着 Array#includes的比较函数与严格的比较函数不同:

22.1.3.13 Array.Prototype.include

注3

包括方法故意在两个方面不同于类似的 索引方法。首先,它使用 SameValueZero算法,而不是 严格的平等比较,允许它检测 阵列元素。其次,它不会跳过缺少的数组元素,而是将它们视为 未定义

眼镜

这似乎是 Number::sameValueZero抽象操作的一部分:

6.1.6.1.15编号: : sameValueZero (X)

  1. 如果 X,返回 没错

[...]

这项操作必须是 Array#includes()检查的一部分,Array#includes()检查的目的是:

22.1.3.13 Array.Prototype.include (SearchElement[ ,来自索引])

[...]

  1. 重复,当 K < Len
    ElementK成为? Get (,! ToString (K))的结果。
    如果 SameValueZero (SearchElementElementK)是 没错,则返回 没错
    将 k 设置为 k + 1。
  2. 返回 假的

[...]

其中,SameValueZero操作将在步骤2中委托给一个用于数字的函数:

7.2.12 SameValueZero (X)

[...]

  1. 如果 Type (X)与 Type ()不同,则返回 假的
  2. 如果 Type (X)是 Number 或 BigInt,则
    Return! Type (X) : : sameValueZero (X).
  3. 返回! SameValueNonNumeric (X)。

作为比较,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)

[...]

  1. 如果 Type (X)是 Number,则 如果 X,返回 没错。 [...]

ECMAScript 5.1只有一个 SameValue算法 ,它仍然将 NaN等同于 NaN。与 SameValueZero的唯一区别是如何处理 +0-0: SameValue为它们返回 false,而 SameValueZero返回 true

SameValue主要用于内部对象操作,因此对于编写 JavaScript 代码来说几乎无关紧要。SameValue的许多用法都是在处理对象键时使用的,而且没有数值。

SameValue操作直接暴露在 ECMAScript 6中,因为这是 Object.is()使用的:

console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0));   // false

略有兴趣的是,WeakMapWeakSet也使用 SameValue,而不是 MapSet用于比较的 SameValueZero。但是,WeakMapWeakSet只允许将对象作为唯一成员,因此试图添加 NaN+0WeakSet0或其他原语会导致错误。

根据 MDN 的文件的说法

注意: 从技术上讲,includes()使用 < strong > sameValueZero 确定是否找到给定元素的算法。

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


更详细的解释:

  1. 同值相等但 + 0和 -0被认为是相等的相似的同-值-零相等性 。
  2. 同值相等 Object.is ()方法提供: Object.is()===之间唯一的区别在 < em > 他们对有符号的零和 NaN 的处理。

enter image description here


额外资源: