为啥 [NaN].includes(NaN) 在 JavaScript 中返回 true?

Posted

技术标签:

【中文标题】为啥 [NaN].includes(NaN) 在 JavaScript 中返回 true?【英文标题】:Why does [NaN].includes(NaN) return true in JavaScript?为什么 [NaN].includes(NaN) 在 JavaScript 中返回 true? 【发布时间】:2021-06-18 22:41:55 【问题描述】:

我熟悉 NaNjavascript 中的“怪异”,即 NaN === NaN 总是返回 false,如 here 所述。所以不应该进行===比较来检查NaN,而是使用isNaN(..)。

所以我很惊讶地发现

> [NaN].includes(NaN)
true

这似乎不一致。为什么会有这种行为?

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

【问题讨论】:

您想阅读这篇文章github.com/tc39/proposal-Array.prototype.includes/issues/29 This seems inconsistent, 是的,看起来确实很奇怪。 NaN 不等于除 X 之外的任何东西,包括 NaN。但我假设在这里选择它是因为如果它没有返回 true,那么在数组中包含 NaN 将毫无意义,因为它永远不会被发现。 @Keith 可以通过.some() 找到。 this 方法被决定与NaN 一起工作,这与其他任何东西都不同,这有点奇怪。除了地图和场景。对于那些,将NaN 视为相同的值确实有意义,否则数据结构将变得非常无用。 【参考方案1】:

据MDN's document这么说

注意:从技术上讲,includes() 使用 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

更详细的解释:

    Same-value-zero equality 类似于 same-value equality,但 +0 和 -0 被认为是相等的相同值相等由 Object.is() 方法提供:Object.is()=== 之间的唯一区别在于它们对有符号零和 NaN 的处理。


其他资源:

Which equals operator (== vs ===) should be used in JavaScript comparisons? Array.prototype.includes vs. Array.prototype.indexOf Are +0 and -0 the same?

【讨论】:

我刚刚添加了additional resources & Re-arrange sample code to make it clearer as well as more understandable。正如我们可能熟悉的lose & strict equality comparison,但我想列出所有这些以帮助我们对它们进行整体比较。 @IMSoP【参考方案2】:

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

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

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

console.log(Object.is(NaN, NaN));

.includes() 方法的行为与.indexOf() 方法略有不同; .indexOf() 方法使用严格相等比较来比较值,并且严格相等比较不认为NaN 与自身相等。

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

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

MDN - Equality comparisons and sameness

【讨论】:

【参考方案3】:

规格

这似乎是Number::sameValueZero abstract operation 的一部分:

6.1.6.1.15 Number::sameValueZero (x, y)

    如果 xNaN 并且 yNaN,则返回 true

[...]

此操作必须是Array#includes() 检查的一部分:

22.1.3.13 Array.prototype.includes (searchElement [ , fromIndex ] )

[...]

    重复,而 k len 一种。让 elementK 成为 ?获取(O, !ToString(k))。 湾。如果 SameValueZero(searchElement, elementK) 为 true,则返回 true。 C。将 k 设置为 k + 1。 返回 false

[...]

SameValueZero operation 将在第 2 步委派给数字:

7.2.12 SameValueZero (x, y)

[...]

    如果 Type(x) 与 Type(y) 不同,则返回 false。 如果 Type(x) 是 Number 或 BigInt,则 一种。返回 !类型(x)::sameValueZero(x, y)。 返回! SameValueNonNumeric(x, y).

为了比较,Array#indexOf() 将使用 Strict Equality Comparison,这就是它表现不同的原因:

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 algorithm first appears in the ECMAScript 6 specifications 但它更冗长。它仍然具有相同的含义,并且仍然具有显式:

7.2.10 SameValueZero(x, y)

[...]

    如果 Type(x) 是数字,那么 一种。如果 xNaN 并且 yNaN,则返回 true。 [...]

ECMAScript 5.1 only has a SameValue algorithm 仍将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 而不是SameValueZeroMapSet 用于比较。但是,WeakMapWeakSet 只允许对象作为唯一成员,因此尝试添加 NaN+0-0 或其他原语会导致错误。

【讨论】:

“如果 xNaN”是否与“如果 isNaN(x)”完全一样?还是仅指NaN 生成的单个 NaN 值,而不是整个 NaN 值集? @BenVoigt 它与if (Number.isNaN(x)) 基本相同。好吧,那将是 JavaScript 的等价物。算法步骤是伪代码。该实现应该检查x 是否是NaN 的文字值。其中只有一个,但可以转换为不同的东西(例如,Number("apple"))。编辑:ref for NaN. IEEE-754 中肯定有多个 NaN 位模式,我相信您可以使用例如将 IEEE-754 值转换为 Javascript。一个类型化的缓冲区。所以我的问题是,SameValueZero 是否将两个不同的 NaN 位模式视为相等或不相等? @BenVoigt 啊,我想这就是我对 IEEE-754 的了解越来越薄的地方。但根据规范NaNNaN,即使它不等于自身。在概念上应该只有一个。我想实现可以随意使用任意数量的NaN 值,但如果算法显示x is NaN,则意味着是任何有效的NaN 值。【参考方案4】:

在7.2.16 Strict Equality Comparison中,有如下注释:

注意

此算法与 SameValue 算法的不同之处在于它对有符号零和 NaN 的处理。

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

22.1.3.13 Array.prototype.includes下

注意 3

includes 方法有意在两个方面与类似的 indexOf 方法不同。首先,它使用SameValueZero 算法,而不是Strict Equality Comparison,允许它检测NaN 数组元素。其次,它不会跳过缺失的数组元素,而不是将它们视为undefined

【讨论】:

【参考方案5】:

正如您在阅读 include documentation 时看到的那样,它确实使用 sameValueZero 算法来工作,因此正如它的 documentation 所说,它在比较 NaN 和 I 引用时给出了 True 值:

我们可以从下面的相同性比较表中看到,这是由于 Object.is 处理 NaN 的方式。请注意,如果 Object.is(NaN, NaN) 评估为假,我们可以说它适合松/严格谱,作为三等式的更严格形式,一种区分 -0 和 +0 的形式。然而,NaN 处理意味着这是不真实的。不幸的是,Object.is 必须根据其具体特征来考虑,而不是在相等运算符方面的松散或严格性。

【讨论】:

以上是关于为啥 [NaN].includes(NaN) 在 JavaScript 中返回 true?的主要内容,如果未能解决你的问题,请参考以下文章

在 javascript 数组中查找 NaN 的索引

为啥在 numpy `nan == nan` 中是 False 而 [nan] 中的 nan 是 True?

为啥 NaN === NaN 是假的? [复制]

为啥 NaN = !NaN 返回真?

为啥 float.NaN != double.NaN 在 C# 中?

为啥 Double.NaN==Double.NaN 返回 false?