为啥 [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 【问题描述】:我熟悉 NaN
在 javascript 中的“怪异”,即 NaN === NaN
总是返回 false
,如 here 所述。所以不应该进行===
比较来检查NaN
,而是使用isNaN(..)。
所以我很惊讶地发现
> [NaN].includes(NaN)
true
这似乎不一致。为什么会有这种行为?
它是如何工作的? includes
方法是否专门检查isNaN
?
【问题讨论】:
您想阅读这篇文章github.com/tc39/proposal-Array.prototype.includes/issues/29This 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)
如果 x 是 NaN 并且 y 是 NaN,则返回 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) 是数字,那么 一种。如果 x 是 NaN 并且 y 是 NaN,则返回 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
比较有趣的是WeakMap
和WeakSet
也使用SameValue
而不是SameValueZero
,Map
和Set
用于比较。但是,WeakMap
和 WeakSet
只允许对象作为唯一成员,因此尝试添加 NaN
或 +0
或 -0
或其他原语会导致错误。
【讨论】:
“如果 x 是 NaN”是否与“如果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 的了解越来越薄的地方。但根据规范NaN
是NaN
,即使它不等于自身。在概念上应该只有一个。我想实现可以随意使用任意数量的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?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 numpy `nan == nan` 中是 False 而 [nan] 中的 nan 是 True?