JavaScript loose equality (==) and type coercion
Never use two equals (==); always use three equals (===): this is received
wisdom in JavaScript. Is that the whole story? Let’s take a look.
In JavaScript, what do these evaluate to?
true == 1;false == '';0 == [0];'1' == true;null == undefined;[] == false;Answer: they are all true. Were any surprising to you? JavaScript uses the
equal sign in three ways.
The first, =, is the assignment operator (const one = 1).
The second, ==, is the equality operator, AKA “twoquals.” It offers comparison
via the “IsLooselyEqual” algorithm.
The third, ===, is the strict equality operator, AKA “threequals.” It offers
comparison via the “IsStrictlyEqual” algorithm. === is the equality comparator
most expect coming from other languages such as Go or Ruby.
The difference that distinguishes == is: if the operands are different types,
the equality operator == attempts to convert them to the same type before
comparing.
How that works is pretty complicated; you can find a description here: Equality Comparison and Sameness.
Let’s consider a concrete breakdown of one such comparison: why is [] == false
true? Loose equality does not compare them as booleans. Roughly:
falsebecomes0(ToNumber)[]becomes""(ToPrimitiveviatoString)""becomes0(ToNumber)0 === 0So[] == falseis0 == 0, which is true
So, should we ever use two equals? Here are two use cases. The first is
comparing a value to null or undefined:
if (value == null) { // Value is either null or undefined}
if (value === null || value === undefined) { // This is equivalent}The second is comparing identifiers that could be numbers or strings, such as those from an API1:
const id = element.dataset.userId; // string
if (id == 42) { // true}I think the first example is more widely accepted. If you find implicit type coercion less readable than the alternative, as I do, I’d suggest avoiding them both.
Take it from Douglas Crockford, JavaScript ecosystem contributor:
“My advice is to never use the evil twins. Instead, always use === and !==.” —Douglas Crockford
Footnotes
-
I wrote about a different way to approach this here: Accepting string or number IDs in TypeScript ↩