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

Yos*_*ein 133 javascript arrays nan ecmascript-6

我熟悉的NaN是在JavaScript中“怪异”的,即NaN === NaN总是返回false,如所描述这里。因此,不应通过===比较来检查NaN,而应使用 isNaN(..) 。

所以我惊讶地发现

> [NaN].includes(NaN)
true
Run Code Online (Sandbox Code Playgroud)

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

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

Ngu*_*ong 144

根据MDN 的文档

注意:从技术上讲,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
Run Code Online (Sandbox Code Playgroud)


更详细的解释:

  1. 同值零相等类似于同值相等但 +0 和 ?0 被认为是相等的
  2. 同值的相等是由提供Object.is()方法:唯一的区别Object.is()===是在他们的治疗符号零和NaN的。

在此处输入图片说明


其他资源:

  • 我刚刚添加了“额外的资源并重新排列示例代码以使其更清晰且更易于理解”。因为我们可能熟悉“失败和严格相等比较”,但我想列出所有这些,以帮助我们对它们进行整体比较。@IMSoP (4认同)

You*_*saf 25

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

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

Object.is()方法使用SameValue并为 返回 true NaN

console.log(Object.is(NaN, NaN));
Run Code Online (Sandbox Code Playgroud)

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

console.log([NaN].indexOf(NaN));
Run Code Online (Sandbox Code Playgroud)

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

MDN - 平等比较和相同


VLA*_*LAZ 19

眼镜

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

6.1.6.1.15 Number::sameValueZero ( x , y )

  1. 如果xNaN并且yNaN,则返回true

[...]

此操作是Array#includes()检查的一部分,它执行以下操作:

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

[...]

  1. 重复,而k < len
    a。让elementK是 ? Get( O , !ToString( k ))。
    湾 如果 SameValueZero( searchElement , elementK ) 为true,则返回true
    C。将 k 设置为 k + 1。
  2. 返回false

[...]

SameValueZero操作将委托给一个在步骤2的数字:

7.2.12 SameValueZero ( x , y )

[...]

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

对于比较Array#indexOf()将使用严格相等比较,这就是为什么它的行为不同:

const arr = [NaN];
console.log(arr.includes(NaN)); // true
console.log(arr.indexOf(NaN));  // -1
Run Code Online (Sandbox Code Playgroud)


其他类似情况

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)


历史

SameValueZero算法首先出现在 ECMAScript 6 规范中,但它更加冗长。它仍然具有相同的含义,并且仍然具有明确的含义:

7.2.10 SameValueZero( x , y )

[...]

  1. 如果 Type( x ) 是 Number,则 a. 如果xNaN并且yNaN,则返回true。[...]

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
Run Code Online (Sandbox Code Playgroud)

稍微值得关注的是,WeakMapWeakSet同样使用SameValue,而不是SameValueZeroMapSet使用进行比较。但是,WeakMap并且WeakSet只允许对象作为唯一成员,因此尝试添加NaN+0-0或其他原语会导致错误。


Nin*_*olz 8

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

笔记

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

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

22.1.3.13 Array.prototype.includes下面

注 3

包括方法有意地从类似的不同的indexOf以两种方式的方法。首先,它使用SameValueZero算法,而不是Strict Equality Comparison,允许它检测NaN数组元素。其次,它不会跳过丢失的数组元素,而是将它们视为undefined