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 comparison
  • Object.is api: SameValue comparison. Added from ES6.

From my practical experience, === is the first comparison you should consider when choosing one from above three types, as it works in every version of 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.


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 about 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 same mathematical semantic meaning and they are same in most of mathematical operations. However, in fact, they are different values and have different byte presentations.

With strict comparison ===, +0 equals to -0, in vice versa, NaN is not equal to itself ( 0 is interpreted as +0). While these comparisons' result are both reversed with Object.is comparison. Check ECMS ( strict equality, SameValue ) specification for reference.


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 you to read the reference to understand my following content in this paragraph.

IEEE 754 double-precision binary floating-point format: binary64

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 its own float value due to the specification of IEEE754 format. In other words, the map from IEEE754 formatted number (or Javascript number) to 64-bit binary array is injective but not surjective.

Thus, there are many 64-bit binary arrays do not have equivalent number value. In Javascript, 2 of them are chosen to be Infinity and -Infinity. All other non-associated values are consider not-a-number ( NaN) instances. Thus, there is not only one but many NaN instance.

I could not found whether ECMA specification defines these special number values' binary presentation from the specs. Thus, I consider the binary presentation varies from implementation to implementation.

Use this script from MDN docs to check for binary presentation of an arbitrary number.

var f2b = x => new Uint8Array(new Float64Array([x]).buffer);
var b2f = x => new Float64Array(x.buffer)[0];

f2b converts number value to 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]

Though out above discussion, we understand that in Javascript, the strict equal === operators give understandable and common sense operation. === 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' result 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 a MDN article


In this post, I have shared my knowledge about equality comparison operators in Javascript. Mostly I discussed about the strict comparison and SameValue comparison.

Please feel free to discuss.