Specs
This appears to be part of the Number::sameValueZero
abstract operation:
6.1.6.1.15 Number::sameValueZero ( x, y )
- If x is NaN and y is NaN, return true.
[...]
This operation is required to be part of the Array#includes()
check which does:
22.1.3.13 Array.prototype.includes ( searchElement [ , fromIndex ] )
[...]
- Repeat, while k < len
a. Let elementK be the result of ? Get(O, ! ToString(k)).
b. If SameValueZero(searchElement, elementK) is true, return true.
c. Set k to k + 1.
- Return false.
[...]
Where the SameValueZero
operation will delegate to the one for numbers at step 2:
7.2.12 SameValueZero ( x, y )
[...]
- If Type(x) is different from Type(y), return false.
- If Type(x) is Number or BigInt, then
a. Return ! Type(x)::sameValueZero(x, y).
- Return ! SameValueNonNumeric(x, y).
For comparison Array#indexOf()
will use Strict Equality Comparison which is why it behaves differently:
const arr = [NaN];
console.log(arr.includes(NaN)); // true
console.log(arr.indexOf(NaN)); // -1
Other similar situations
Other operations that use SameValueZero
for comparison are in sets and maps:
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
History
The SameValueZero
algorithm first appears in the ECMAScript 6 specifications but it is more verbose. It still has the same meaning and still has an explicit:
7.2.10 SameValueZero(x, y)
[...]
- If Type(x) is Number, then
a. If x is NaN and y is NaN, return true.
[...]
ECMAScript 5.1 only has a SameValue
algorithm which still treats NaN
equal to NaN
. The only difference with SameValueZero
is how +0
and -0
are treated: SameValue
returns false
for them, while SameValueZero
returns true
.
SameValue
is mostly used for internal object operation, so it is almost inconsequential for writing JavaScript code. A lot of the uses of SameValue
are when working with object keys and there are no numeric values.
The SameValue
operation is directly exposed in ECMAScript 6 as that is what Object.is()
uses:
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is(+0, -0)); // false
Of slight interest is that WeakMap
and WeakSet
also use SameValue
rather than SameValueZero
that Map
and Set
use for comparison. However, WeakMap
and WeakSet
only allow objects as unique members, so attempting to add a NaN
or +0
or -0
or other primitives leads to an error.
This seems inconsistent,
Yes, it does seem strange. NaN is not equal to anything including NaN, apart from X. But I assume it's chosen here because if it didn't return true, having NaN in the array would then be pointless as it would never be found. – Ridgway.some()
. It's just a bit strange that this method was decided to actually work withNaN
which is different to basically anything else. Aside from map and set. For those, it does make sense to treatNaN
as the same value, otherwise the data structures become very useless. – Militarist