Javascript equality and sameness
This is the second post in my Javascript serial.
There are 3 types of comparison operators in Javascript
===
(most recommend): strict comparison or identity comparison==
(not recommend): loose comparisonObject.is
api: SameValue comparison. Added from ES6.
From my practical experience, ===
is the first comparison you should consider when choosing one from the above three types, as it works in every version of the Javascript implementation.
==
is indeed discouraged in default ESlint configuration.
Object.is
is added from ES6. You should check compatibility and apply polyfill if your client's browser does not support it.
What is the difference?
Because ==
is not so useful and it will raise my eslint warning when being used. I have never used or seen any use case of the loose comparison ==
operator in real life. Thus, I will not discuss the loose comparison operator here.
The main difference between ===
and Object.is
is how do they handle these 3 special primitive number values: +0
, -0
, and NaN
.
+0
and -0
have the same mathematical semantic meaning and they are the same in most mathematical operations. However, in fact, they are different values and have different byte presentations.
With strict comparison ===
, +0
equals to -0
, vice versa. NaN
is not equal to itself ( 0
is interpreted as +0
). While these comparisons' results are both reversed with Object.is
comparison. Check ECMS ( strict equality, SameValue ) specification for reference.
0 === -0 // true
Object.is(0, -0) // false
1 / 0 // Infinity
1 / -0 // -Infinity
NaN === NaN // false
Object.is(NaN, NaN) // true
Why +0, -0, and NaN?
To understand this specification, you should have a look at double-precision binary floating-point format binary64. It is not required for you to read the reference to understand the following content in this paragraph.
In Javascript, the fact that number is stored in IEEE754 format ( ref) implies that all numbers in Javascript have theirs own 64 bit (i.e. 8 bytes) presentations.
However, this does not mean that all 64-bit binary arrays have their own float value due to the specification of the IEEE754 format. In other words, the map from IEEE754 formatted number (or Javascript number) to 64-bit binary array is injective but not surjective.
Hence, there are many 64-bit binary arrays that do not have an equivalent numerical value. In Javascript, 2 of them are chosen to be Infinity
and -Infinity
. All other non-associated values are considered not-a-number ( NaN
) instances. Thus, there is not only one but many NaN
instances.
I could not find whether the ECMA specification defines these special number values' binary presentation from the specs. It is probably that the binary presentation varies from implementation to implementation.
Use this script to check for the binary presentation of an arbitrary number.
f2b
converts number value to an 8-length byte array, b2f
converts Unit8Array
instance to a number. Using f2b
, we can check for default NaN
instance, Infinity
and -Infinity
binary representations.
f2b(NaN) //Uint8Array(8) [0, 0, 0, 0, 0, 0, 248, 127]
f2b(Infinity) //Uint8Array(8) [0, 0, 0, 0, 0, 0, 240, 127]
f2b(+Infinity) //Uint8Array(8) [0, 0, 0, 0, 0, 0, 240, 127]
f2b(-Infinity) //Uint8Array(8) [0, 0, 0, 0, 0, 0, 240, 255]
f2b(0) //[0, 0, 0, 0, 0, 0, 0, 0]
f2b(+0) //[0, 0, 0, 0, 0, 0, 0, 0]
f2b(-0) //[0, 0, 0, 0, 0, 0, 0, 128]
Throughout the above discussion, we understand that in Javascript, the strict equal ===
operators give understandable and common sense operations. ===
considers all values are different from NaN
even the NaN
itself. And the only case in which x !== x
is true
is when x
is NaN
, in a sense that we do not know which instance the NaN
value is.
There are 2 ways to check if a value is NaN
in Javascript. Via Object.is(NaN, x)
or isNaN(x)
.
Quirk
Check these expressions' results from your browser. This quirk requires your browser to support ES6 Array.prototype.includes().
let x = 'hello'
[x].includes(x) // true
[x].indexOf(x) // 0
x = NaN
[x].includes(x) // true
[x].indexOf(x) // -1
The result can be explained by the fact that .includes()
use Object.is
comparison, in the contrast of strict equal ===
comparison used by .indexOf()
.
Bonus image from an MDN article
In this post, I have shared my knowledge about equality comparison operators in Javascript. Mostly I discussed the strict comparison and SameValue comparison.
Please feel free to discuss.