the pleasures and perils of JavaScript’s promiscuous comparison operator
¶ by Rob FrieselSpoiler alert: toString
has its mitts in everything.
This all started when we saw a candidate put the following into an exercise we had given him:
if (arrayToTest == '')
Our first thought was well that seems wrong… And though it seems like something that shouldn’t work, it did–when the array contained a single item, and that single item was an empty string 1.
This realization led to one of those eye-popping moments where you find it necessary to poke and prod at JavaScript’s inner machinations, to sort through the apparent inconsistencies, to make a best-effort at getting your mind around the myriad ways that JavaScript’s loose typing is both a blessing and a curse. Thus:
The first comparison ought to make sense 2. Though the arrays have the same contents, they are different arrays because they are different array literals. So far so good, but now the exciting quirks begin.
In the second example, we might think: OK, the ==
operator is popping off that first (and only) item and comparing it against the right-hand value. Which is close but not quite right (and more on that in a moment). But let’s assume for a moment that that is true (which it’s not), that ==
is popping off the that first/only item. If that were true, then the third example (with the ===
) ought to work; but it doesn’t. And that should not be a surprise; ===
requires the compared items to be the same type, and an array and a number are definitely not the same type. But that leads us to the fourth example and an important hint about what’s going on.
In that fourth example, it starts to become clear. Is ==
popping off the only element in the array and then coercing/casting it from a number to a string in much the same way as 1 == '1'
? We’re getting warmer (but we already know that the answer is “no”). The ==
allows such a promiscuous comparison that it will coerce twice if necessary to get a truthy value. In light of that, consider that this (fourth) example actually has only one type coercion: valueOf()
3 is called on the array to give back (effectively) '1'
. Compare that with the second example where the array (on the left-hand side) is cast via valueOf()
and then the number 1
(on the right) is cast with toString()
as well.
Need more convincing? Check out the fifth and sixth examples.
But then things get weird when you start comparing with Boolean literals.
Given the previous examples, one might expect [true] == true
to evaluate to true
; the array will fall victim to that promiscuous valueOf()
, and the true
literal will get caught by toString()
. Right? Not so fast. That evaluates to false
; sanity check true == true
if you want — but bear in mind that that is not the same comparison. But neither is [true] == 'true'
— in that example, valueOf()
plays on the array and the comparison is a success.
By now you’re probably asking: what the hell is going on?
Another quirk. Whereas basically all of the other types are coerced/cast via toString()
or the one-two punch of valueOf()
/toString()
, other rules apply to the Booleans. Booleans are cast as follows: true
becomes 1
and false
becomes 0
— and neither are cast/coerced any further.
All terribly interesting trivia. And it is good knowledge to have when you run into this sort of esotetic thing. But really… if you’re intentionally comparing dissimilar types like that: you’re doing it wrong 4.
- Which (of course) was the whole point of that
if
in the first place.[↩] - If it does not, do yourself a favor and review JavaScript’s reference types. The devil is in those details.[↩]
- For all intents and purposes,
valueOf()
on an array is effectively wrappingtoString()
.[↩] - And by that of course I mean: Don’t Be Too Clever.[↩]
Leave a Reply