为什么[NaN].includes(NaN)在JavaScript中返回true?
我熟悉的NaN是在JavaScript中“怪异”的,即NaN === NaN总是返回false,如所描述这里。因此,不应通过===比较来检查NaN,而应使用 isNaN(..) 。
所以我惊讶地发现
> [NaN].includes(NaN)
true
这似乎不一致。为什么会有这种行为?
它是如何工作的?请问includes方法专门检查isNaN?
回答
根据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
更详细的解释:
- 同值零相等类似于同值相等,但 +0 和 ?0 被认为是相等的。
- 同值的相等是由提供Object.is()方法:唯一的区别
Object.is()和===是在他们的治疗符号零和NaN的。
其他资源:
- 在 JavaScript 比较中应该使用哪个等于运算符 (== vs ===)?
- Array.prototype.includes 与 Array.prototype.indexOf
- +0 和 -0 一样吗?
- I've just added `additional resources & Re-arrange sample code to make it clearer as well as more understandable`. As we may be familiar with `lose & strict equality comparison`, but I want to list out all of them to help us have the overall comparison between them. @IMSoP
回答
该.includes()方法使用SameValueZero算法来检查两个值的相等性,并认为该NaN值等于自身。
的SameValueZero算法类似于SameValue,但唯一的区别是,SameValueZero算法考虑+0和-0是相等的。
该Object.is()方法使用SameValue并为 返回 true NaN。
console.log(Object.is(NaN, NaN));
.includes()方法的行为与方法略有不同.indexOf();该.indexOf()方法使用严格相等比较来比较值,严格相等比较不认为NaN等于自身。
console.log([NaN].indexOf(NaN));
可以在 MDN 上找到有关不同相等性检查算法的信息:
MDN - 平等比较和相同
回答
眼镜
这似乎是Number::sameValueZero抽象操作的一部分:
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
a。让elementK是 ? Get( O , !ToString( k ))。
湾 如果 SameValueZero( searchElement , elementK ) 为true,则返回true。
C。将 k 设置为 k + 1。- 返回false。
[...]
当SameValueZero操作将委托给一个在步骤2的数字:
7.2.12 SameValueZero ( x , y )
[...]
- 如果 Type( x ) 与 Type( y ) 不同,则返回false。
- 如果 Type( x ) 是 Number 或 BigInt,则
a. 返回 !类型( x )::sameValueZero( x , y )。- 返回 !SameValueNonNumeric( x , y )。
对于比较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 , y )
[...]
- 如果 Type( x ) 是 Number,则 a. 如果x是NaN并且y是NaN,则返回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
稍微值得关注的是,WeakMap与WeakSet同样使用SameValue,而不是SameValueZero说Map和Set使用进行比较。但是,WeakMap并且WeakSet只允许对象作为唯一成员,因此尝试添加NaN或+0或-0或其他原语会导致错误。
回答
在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。